Skip to content
Draft
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 .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "18.20.3"
node-version: "22.22.0"
cache: "npm"
cache-dependency-path: |
ui/ui-frontend/package-lock.json
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/i18n-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "18.20.3"
node-version: "22.22.0"
- name: Run i18n comparison script
working-directory: tools
run: node check-i18n.js
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18.20.3'
node-version: '22.22.0'
- name: Check missing icon in icomoon
run: ./tools/check_icomoon.sh
# Only install prettier globally (with same version as in package.json)
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-design-system.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "18.20.3"
node-version: "22.22.0"
cache: "npm"
cache-dependency-path: |
ui/ui-frontend/package-lock.json
Expand Down
2 changes: 1 addition & 1 deletion Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ pipeline {
sh 'sudo apt install -y build-essential make ruby ruby-dev rubygems jq'
sh 'sudo timedatectl set-timezone Europe/Paris'
sh 'sudo gem install fpm'
nvm('v18.20.3') { // We're installing correct Node version through NVM then update the path to make it available. Do NOT wrap your code in `nvm('...') {}` as it would override the whole PATH and then break tools (jdk, maven) configurations
nvm('v22.22.0') { // We're installing correct Node version through NVM then update the path to make it available. Do NOT wrap your code in `nvm('...') {}` as it would override the whole PATH and then break tools (jdk, maven) configurations
script {
nvmPath = sh(script: 'dirname $(which node)', returnStdout: true).trim()
env.PATH = "${nvmPath}:${env.PATH}"
Expand Down
2 changes: 2 additions & 0 deletions api/api-gateway/src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ spring:
/collect-api/users/analytics,
/collect-api/logbooks/operations/**,
/collect-api/accesscontracts/**,
/collect-api/discussions/**,

/ingest-api/ui/applications/**,
/ingest-api/externalparameters/**,
Expand Down Expand Up @@ -146,6 +147,7 @@ spring:
- RewritePath=/collect-api/userinfos(?<segment>.*),/iam/v1/userinfos$\{segment},
- RewritePath=/collect-api/users(?<segment>.*),/iam/v1/users$\{segment},
- RewritePath=/collect-api/subrogations/(?<segment>.*),/iam/v1/subrogations/$\{segment},
- RewritePath=/collect-api/discussions(?<segment>.*),/iam/v1/discussions$\{segment},
- RewritePath=/collect-api/(?<segment>.*),/v1/$\{segment}

# Ingest IAM API
Expand Down
4 changes: 4 additions & 0 deletions api/api-iam/iam/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package fr.gouv.vitamui.iam.server.discussion.dao;

import fr.gouv.vitamui.commons.mongo.repository.VitamUIRepository;
import fr.gouv.vitamui.iam.server.discussion.domain.DiscussionRead;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

/**
* Repository for DiscussionRead.
*/
@Repository
public interface DiscussionReadRepository extends VitamUIRepository<DiscussionRead, String> {
Optional<DiscussionRead> findByUserIdAndDiscussionId(String userId, String discussionId);

List<DiscussionRead> findByUserIdAndDiscussionIdIn(String userId, List<String> discussionIds);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package fr.gouv.vitamui.iam.server.discussion.dao;

import fr.gouv.vitamui.commons.mongo.repository.VitamUIRepository;
import fr.gouv.vitamui.iam.server.discussion.domain.Discussion;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
* Repository for Discussions.
*/
@Repository
public interface DiscussionRepository extends VitamUIRepository<Discussion, String> {
List<Discussion> findByEntitiesEntityIdAndEntitiesEntityType(String entityId, String entityType);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package fr.gouv.vitamui.iam.server.discussion.domain;

import fr.gouv.vitamui.commons.api.domain.BaseIdentifierDocument;
import fr.gouv.vitamui.commons.mongo.IdDocument;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

/**
* Discussion document.
*/
@Document(collection = "discussions")
@Getter
@Setter
@ToString
public class Discussion extends IdDocument implements BaseIdentifierDocument {

private String identifier; // Using this as the 'id' for BaseIdentifierDocument compliance if needed, or
// mapped to internal logic

private List<EntityLink> entities = new ArrayList<>();

private String title;

private String status;

private Instant lastMessageAt;

private List<Message> messages = new ArrayList<>();

@Getter
@Setter
@ToString
public static class EntityLink {

private String entityId;
private String entityType;
}

@Getter
@Setter
@ToString
public static class Message {

private String id;
private String authorUserInfoId;
private String text;
private Instant createdAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package fr.gouv.vitamui.iam.server.discussion.domain;

import fr.gouv.vitamui.commons.mongo.IdDocument;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.Instant;

/**
* Discussion Read status document.
*/
@Document(collection = "discussions_read")
@CompoundIndex(def = "{'userId': 1, 'discussionId': 1}", unique = true)
@Getter
@Setter
@ToString
public class DiscussionRead extends IdDocument {

private String userId;

private String discussionId;

private Instant lastReadAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package fr.gouv.vitamui.iam.server.discussion.rest;

import fr.gouv.vitamui.iam.server.discussion.domain.Discussion;
import fr.gouv.vitamui.iam.server.discussion.service.DiscussionService;
import lombok.Getter;
import lombok.Setter;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.util.List;

/**
* Controller for Discussion API.
*/
@RestController
@RequestMapping("/iam/v1/discussions")
public class DiscussionController {

private final DiscussionService discussionService;

public DiscussionController(DiscussionService discussionService) {
this.discussionService = discussionService;
}

@GetMapping
public List<DiscussionDto> findDiscussions(@RequestParam String entityId, @RequestParam String entityType) {
return discussionService.findDiscussions(entityId, entityType);
}

@PostMapping
public Discussion createDiscussion(@RequestBody CreateDiscussionRequest request) {
return discussionService.createDiscussion(request.getTitle(), request.getEntities());
}

@PostMapping("/{discussionId}/messages")
public Discussion addMessage(@PathVariable String discussionId, @RequestBody AddMessageRequest request) {
return discussionService.addMessage(discussionId, request.getText());
}

@PutMapping("/{discussionId}/resolve")
public void resolveDiscussion(@PathVariable String discussionId) {
discussionService.resolveDiscussion(discussionId);
}

@PutMapping("/{discussionId}/unresolve")
public void unresolveDiscussion(@PathVariable String discussionId) {
discussionService.unresolveDiscussion(discussionId);
}

@PutMapping("/{discussionId}/read")
public void markAsRead(@PathVariable String discussionId) {
discussionService.markAsRead(discussionId);
}

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<Discussion>> streamDiscussions(
@RequestParam(required = false) String entityId,
@RequestParam(required = false) String entityType
) {
return discussionService
.getDiscussionChangeStream()
.filter(discussion -> {
if (entityId != null && entityType != null) {
return discussion
.getEntities()
.stream()
.anyMatch(
entity -> entityId.equals(entity.getEntityId()) && entityType.equals(entity.getEntityType())
);
}
return true;
})
.map(discussion -> ServerSentEvent.builder(discussion).build());
}

@Getter
@Setter
public static class CreateDiscussionRequest {

private String title;
private List<Discussion.EntityLink> entities;
}

@Getter
@Setter
public static class AddMessageRequest {

private String text;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package fr.gouv.vitamui.iam.server.discussion.rest;

import fr.gouv.vitamui.iam.server.discussion.domain.Discussion;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.time.Instant;

/**
* DTO for Discussion with Read Status.
*/
@Getter
@Setter
@ToString
public class DiscussionDto {

private Discussion discussion;
private Instant lastReadAt;
private boolean isUnread; // Helper flag for UI

public DiscussionDto(Discussion discussion, Instant lastReadAt) {
this.discussion = discussion;
this.lastReadAt = lastReadAt;
this.isUnread = calculateUnread(discussion, lastReadAt);
}

private boolean calculateUnread(Discussion discussion, Instant lastReadAt) {
if (discussion.getLastMessageAt() == null) {
return false;
}
if (lastReadAt == null) {
return true;
}
return discussion.getLastMessageAt().isAfter(lastReadAt);
}
}
Loading
Loading