auth readded
This commit is contained in:
parent
c4a1a7ce98
commit
ea25273601
|
|
@ -3,6 +3,8 @@ package it.cnr.isti.workflow.manager;
|
|||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||
|
||||
import it.cnr.isti.workflow.manager.WorkflowManagerApplication;
|
||||
|
||||
public class ServletInitializer extends SpringBootServletInitializer {
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
package it.cnr.isti.workflow.manager;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import it.cnr.isti.workflow.manager.auth.services.UserImportComponent;
|
||||
|
||||
@SpringBootApplication
|
||||
public class WorkflowManagerApplication {
|
||||
|
||||
@Autowired
|
||||
UserImportComponent userImportComponent;
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(WorkflowManagerApplication.class, args);
|
||||
|
|
@ -18,7 +23,8 @@ public class WorkflowManagerApplication {
|
|||
@ConditionalOnProperty(prefix = "app", name = "db.init.enabled", havingValue = "true")
|
||||
CommandLineRunner init() {
|
||||
return args -> {
|
||||
|
||||
System.out.println("Initializing database...");
|
||||
userImportComponent.start();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package it.cnr.isti.workflow.manager.app;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@Component
|
||||
public class ObjectMapperHolder {
|
||||
public static ObjectMapper mapper;
|
||||
|
||||
public ObjectMapperHolder(ObjectMapper objectMapper) {
|
||||
ObjectMapperHolder.mapper = objectMapper;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package it.cnr.isti.workflow.manager;
|
||||
package it.cnr.isti.workflow.manager.app;
|
||||
|
||||
public interface Validator<T> {
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package it.cnr.isti.workflow.manager.app.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme.Type;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
|
||||
@Bean
|
||||
OpenAPI customOpenAPI() {
|
||||
final String securitySchemeName = "bearerAuth";
|
||||
|
||||
return new OpenAPI()
|
||||
.info(new Info().title("API Docs").version("v1"))
|
||||
.components(new io.swagger.v3.oas.models.Components()
|
||||
.addSecuritySchemes(securitySchemeName,
|
||||
new SecurityScheme()
|
||||
.name(securitySchemeName)
|
||||
.type(Type.HTTP)
|
||||
.scheme("bearer")
|
||||
.bearerFormat("JWT")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package it.cnr.isti.workflow.manager.app.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import jakarta.validation.Validator;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Value("${cors.allowed-origins}")
|
||||
private String allowedOrigins;
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(@NonNull CorsRegistry registry) {
|
||||
String[] origins = allowedOrigins.split(",");
|
||||
|
||||
registry.addMapping("/**")
|
||||
.allowedOrigins(origins)
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Validator validator() {
|
||||
return new LocalValidatorFactoryBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package it.cnr.isti.workflow.manager.auth.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import it.cnr.isti.workflow.manager.auth.repo.AuthRepository;
|
||||
import it.cnr.isti.workflow.manager.auth.repo.LoginEntity;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired
|
||||
private JwtUtil jwtUtil;
|
||||
|
||||
@Autowired
|
||||
private AuthRepository authRepository;
|
||||
|
||||
private static final List<String> EXCLUDED_PATHS = List.of("/auth/", "/health");
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
String path = request.getRequestURI();
|
||||
|
||||
if (EXCLUDED_PATHS.stream().anyMatch(path::startsWith)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
|
||||
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||
String jwt = authHeader.substring(7);
|
||||
String username = jwtUtil.extractUsername(jwt);
|
||||
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
LoginEntity userDetails = authRepository.findById(username).orElse(null);
|
||||
|
||||
if (jwtUtil.validateToken(jwt, userDetails)) {
|
||||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails,
|
||||
null, Collections.emptyList());
|
||||
|
||||
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package it.cnr.isti.workflow.manager.auth.config;
|
||||
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import it.cnr.isti.workflow.manager.auth.repo.LoginEntity;
|
||||
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
SecretKey secretKey;
|
||||
|
||||
public JwtUtil( @Value("${app.security.key}") String secretKeyString) {
|
||||
this.secretKey = Keys.hmacShaKeyFor(secretKeyString.getBytes());
|
||||
}
|
||||
|
||||
public String generateToken(String username) {
|
||||
String jwt = Jwts.builder()
|
||||
.setSubject(username)
|
||||
.setIssuedAt(new Date())
|
||||
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1 giorno
|
||||
.signWith(secretKey, SignatureAlgorithm.HS256) // Usa una SecretKey (HMAC)
|
||||
.compact();
|
||||
return jwt;
|
||||
}
|
||||
|
||||
public String extractUsername(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody()
|
||||
.getSubject();
|
||||
}
|
||||
|
||||
public boolean validateToken(String token, LoginEntity userDetails) {
|
||||
final String username = extractUsername(token);
|
||||
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
|
||||
}
|
||||
|
||||
private boolean isTokenExpired(String token) {
|
||||
Date expiration = Jwts.parserBuilder()
|
||||
.setSigningKey(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody()
|
||||
.getExpiration();
|
||||
return expiration.before(new Date());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package it.cnr.isti.workflow.manager.auth.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Autowired
|
||||
private JwtAuthenticationFilter jwtFilter;
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
return http
|
||||
.cors(Customizer.withDefaults())
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/auth/**").permitAll()
|
||||
.requestMatchers("/swagger-ui.html").permitAll()
|
||||
.requestMatchers("/swagger-ui/**").permitAll()
|
||||
.requestMatchers("/v3/api-docs/**").permitAll()
|
||||
.anyRequest().authenticated())
|
||||
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||
return config.getAuthenticationManager();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package it.cnr.isti.workflow.manager.auth.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class AuthRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
// Getters e Setters
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
|
||||
boolean isValid() {
|
||||
return username.contains(" ") && username != null && !username.isEmpty() && username.length()>5 &&
|
||||
password != null && username.contains(" ") && password.length()>5 && !password.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package it.cnr.isti.workflow.manager.auth.model;
|
||||
|
||||
|
||||
public class ChangePasswordRequest {
|
||||
private String username;
|
||||
private String oldPassword;
|
||||
private String newPassword;
|
||||
|
||||
// Getters e Setters
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
|
||||
public String getOldPassword() { return oldPassword; }
|
||||
public void setOldPassword(String oldPassword) { this.oldPassword = oldPassword; }
|
||||
|
||||
public String getNewPassword() { return newPassword; }
|
||||
public void setNewPassword(String newPassword) { this.newPassword = newPassword; }
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package it.cnr.isti.workflow.manager.auth.repo;
|
||||
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
|
||||
@Repository
|
||||
public interface AuthRepository extends JpaRepository<LoginEntity, String> {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package it.cnr.isti.workflow.manager.auth.model;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LoginEntity {
|
||||
@Id
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package it.cnr.isti.workflow.manager.auth.services;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import it.cnr.isti.workflow.manager.auth.repo.AuthRepository;
|
||||
import it.cnr.isti.workflow.manager.auth.repo.LoginEntity;
|
||||
|
||||
import static it.cnr.isti.workflow.manager.auth.services.PasswordHasher.*;
|
||||
|
||||
@Service
|
||||
public class AuthService {
|
||||
|
||||
|
||||
@Autowired
|
||||
AuthRepository authRepository;
|
||||
|
||||
public boolean validateUser(String username, String password) throws UsernameNotFoundException {
|
||||
LoginEntity le = authRepository.findById(username).orElse(null);
|
||||
if (le == null) {
|
||||
throw new UsernameNotFoundException("User not found");
|
||||
}
|
||||
return checkPassword(password, le.getPassword()); // Validate password
|
||||
|
||||
}
|
||||
|
||||
public boolean registerUser(String username, String password) {
|
||||
if (authRepository.existsById(username)) {
|
||||
return false; // User already exists
|
||||
}
|
||||
String hashedPassword = hashPassword(password);
|
||||
authRepository.save(new LoginEntity(username, hashedPassword));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean changePassword(String username, String oldPassword, String newPassword) {
|
||||
return authRepository.findById(username)
|
||||
.filter(user -> checkPassword(oldPassword, user.getPassword()))
|
||||
.map(user -> {
|
||||
user.setPassword(hashPassword(newPassword));
|
||||
authRepository.save(user);
|
||||
return true;
|
||||
})
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package it.cnr.isti.workflow.manager.auth.services;
|
||||
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
|
||||
public class PasswordHasher {
|
||||
|
||||
private static final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
public static String hashPassword(String plainPassword) {
|
||||
return passwordEncoder.encode(plainPassword);
|
||||
}
|
||||
|
||||
public static boolean checkPassword(String plainPassword, String hashedPassword) {
|
||||
return passwordEncoder.matches(plainPassword, hashedPassword);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package it.cnr.isti.workflow.manager.auth.services;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import it.cnr.isti.workflow.manager.app.ObjectMapperHolder;
|
||||
import it.cnr.isti.workflow.manager.auth.repo.AuthRepository;
|
||||
import it.cnr.isti.workflow.manager.auth.repo.LoginEntity;
|
||||
|
||||
@Component
|
||||
public class UserImportComponent {
|
||||
|
||||
private static final Logger logger = org.slf4j.LoggerFactory.getLogger(UserImportComponent.class);
|
||||
|
||||
private static final String USERS_FILE_NAME = "users.json";
|
||||
|
||||
@Autowired
|
||||
AuthRepository authRepository;
|
||||
|
||||
@Value("${app.import.enabled}")
|
||||
boolean enabled;
|
||||
|
||||
@Value("${app.import.path}")
|
||||
String path;
|
||||
|
||||
public void start() {
|
||||
if (!enabled) {
|
||||
logger.warn("ImportComponent is disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
File usersFile = new File(path, USERS_FILE_NAME);
|
||||
|
||||
if (usersFile.exists())
|
||||
insertUser(usersFile);
|
||||
else
|
||||
logger.warn("Users file does not exist: {}", usersFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
private void insertUser(File file) {
|
||||
try {
|
||||
List<LoginEntity> users = ObjectMapperHolder.mapper.readValue(file,
|
||||
ObjectMapperHolder.mapper.getTypeFactory().constructCollectionType(List.class, LoginEntity.class));
|
||||
for (LoginEntity user : users) {
|
||||
authRepository.findById(user.getUsername()).ifPresentOrElse(
|
||||
existingUser -> {
|
||||
logger.debug("User {} already present in the database, updating password",
|
||||
user.getUsername());
|
||||
existingUser.setPassword(PasswordHasher.hashPassword(user.getPassword()));
|
||||
authRepository.save(existingUser);
|
||||
},
|
||||
() -> {
|
||||
logger.debug("User {} not found, saving new user", user.getUsername());
|
||||
user.setPassword(PasswordHasher.hashPassword(user.getPassword()));
|
||||
authRepository.save(user);
|
||||
});
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error loading users: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
package it.cnr.isti.workflow.manager.blocks.types;
|
||||
|
||||
|
||||
|
||||
import it.cnr.isti.workflow.manager.blocks.configurations.BlockConfiguration;
|
||||
|
||||
public interface BlockType {
|
||||
|
||||
String getName();
|
||||
|
|
@ -8,5 +12,5 @@ public interface BlockType {
|
|||
|
||||
boolean validate();
|
||||
|
||||
String getBlockConfigurationSchema();
|
||||
Class<? extends BlockConfiguration<?>> getBlockConfigurationClass();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package it.cnr.isti.workflow.manager.blocks.types;
|
|||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import it.cnr.isti.workflow.manager.blocks.configurations.BlockConfiguration;
|
||||
|
||||
@Component(HumanInteractionBlockType.TYPE)
|
||||
public class HumanInteractionBlockType implements BlockType {
|
||||
|
||||
|
|
@ -23,7 +25,7 @@ public class HumanInteractionBlockType implements BlockType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getBlockConfigurationSchema() {
|
||||
public Class<? extends BlockConfiguration<?>> getBlockConfigurationClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@ package it.cnr.isti.workflow.manager.blocks.types;
|
|||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import it.cnr.isti.workflow.manager.blocks.configurations.BlockConfiguration;
|
||||
import it.cnr.isti.workflow.manager.blocks.configurations.LLMBlockConfiguration;
|
||||
|
||||
@Component(LLMBlockType.TYPE)
|
||||
public class LLMBlockType implements BlockType {
|
||||
|
||||
|
|
@ -23,8 +26,8 @@ public class LLMBlockType implements BlockType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getBlockConfigurationSchema() {
|
||||
return "Inputs: AT_LEAST_ONE, Outputs: SINGLE, Models: SINGLE";
|
||||
public Class<? extends BlockConfiguration<?>> getBlockConfigurationClass() {
|
||||
return LLMBlockConfiguration.class;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package it.cnr.isti.workflow.manager.blocks.types;
|
|||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import it.cnr.isti.workflow.manager.blocks.configurations.BlockConfiguration;
|
||||
|
||||
@Component(SourceBlockType.TYPE)
|
||||
public class SourceBlockType implements BlockType {
|
||||
|
||||
|
|
@ -23,8 +25,8 @@ public class SourceBlockType implements BlockType {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getBlockConfigurationSchema() {
|
||||
return null;
|
||||
public Class<? extends BlockConfiguration<?>> getBlockConfigurationClass() {
|
||||
return null; // Assuming no specific configuration class for SourceBlockType
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@ import lombok.NoArgsConstructor;
|
|||
public abstract class Brick {
|
||||
|
||||
private String id;
|
||||
private String brickProvider;
|
||||
private String brickManager;
|
||||
|
||||
protected Brick(String id, String brickProvider) {
|
||||
this.brickProvider = brickProvider;
|
||||
this.id = this.brickProvider + ":" + id;
|
||||
public Brick(String id, String brickManager) {
|
||||
this.brickManager = brickManager;
|
||||
this.id = this.brickManager + ":" + id;
|
||||
}
|
||||
|
||||
public String getBrickProvider() {
|
||||
return brickProvider;
|
||||
public String getBrickManager() {
|
||||
return brickManager;
|
||||
}
|
||||
|
||||
public Map<String, Object> getExecutionTimeConfiguration() {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import lombok.NoArgsConstructor;
|
|||
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
|
||||
public abstract class BrickManager<T extends Brick> {
|
||||
|
||||
public abstract List<String> possibleValues(String key);
|
||||
|
||||
public abstract List<T> getBricks();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ public class LLMBrick extends Brick {
|
|||
private final String model;
|
||||
|
||||
|
||||
public LLMBrick(String provider, String model, String brickProvider) {
|
||||
super(provider + "_" + model, brickProvider);
|
||||
public LLMBrick(String provider, String model, String brickManager) {
|
||||
super(provider + "_" + model, brickManager);
|
||||
this.provider = provider;
|
||||
this.model = model;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,10 +25,5 @@ public class LLMBrickManager extends BrickManager<LLMBrick> {
|
|||
.forEach(brick -> bricks.add(brick)));
|
||||
return bricks;
|
||||
}
|
||||
|
||||
public List<String> possibleValues(String key) {
|
||||
return List.of("model1", "model2", "model3"); // Example values, replace with actual logic
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
package it.cnr.isti.workflow.manager.bricks.configurations;
|
||||
|
||||
public interface BrickConfiguration {
|
||||
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package it.cnr.isti.workflow.manager.bricks.configurations;
|
||||
|
||||
import it.cnr.isti.workflow.manager.schema.DynamicMultiValues;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class LLMBrickConfiguration implements BrickConfiguration {
|
||||
|
||||
@NotBlank
|
||||
String provider;
|
||||
|
||||
@DynamicMultiValues(key = "models", provider = "llmBrickProvider")
|
||||
String model;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package it.cnr.isti.workflow.manager.controllers;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import it.cnr.isti.workflow.manager.auth.config.JwtUtil;
|
||||
import it.cnr.isti.workflow.manager.auth.model.AuthRequest;
|
||||
import it.cnr.isti.workflow.manager.auth.model.ChangePasswordRequest;
|
||||
import it.cnr.isti.workflow.manager.auth.services.AuthService;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class AuthController {
|
||||
|
||||
private static final Logger logger = org.slf4j.LoggerFactory.getLogger(AuthController.class);
|
||||
|
||||
@Autowired
|
||||
private JwtUtil jwtUtil;
|
||||
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<?> login(@RequestBody AuthRequest request) {
|
||||
logger.info("Login attempt for user: {}", request.getUsername());
|
||||
if (request.getUsername() == null || request.getPassword() == null) {
|
||||
return ResponseEntity.badRequest().body("Username and password are required");
|
||||
}
|
||||
try {
|
||||
if (!authService.validateUser(request.getUsername(), request.getPassword())) {
|
||||
return ResponseEntity.status(401).body("Invalid password");
|
||||
}
|
||||
String jwt = jwtUtil.generateToken(request.getUsername());
|
||||
return ResponseEntity.ok(Collections.singletonMap("token", jwt));
|
||||
} catch (UsernameNotFoundException e) {
|
||||
return ResponseEntity.status(404).body("User "+request.getUsername()+" not found");
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<?> register(@RequestBody AuthRequest request) {
|
||||
logger.info("Register attempt for user {} with pwd {}", request.getUsername(), request.getPassword());
|
||||
if (request.getUsername() == null || request.getPassword() == null) {
|
||||
return ResponseEntity.badRequest().body("Username and password are required");
|
||||
}
|
||||
if (request.getUsername().length() < 3 || request.getPassword().length() < 6) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body("Username must be at least 3 characters and password at least 6 characters");
|
||||
}
|
||||
if (authService.registerUser(request.getUsername(), request.getPassword())) {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
return ResponseEntity.badRequest().body("the user already exists");
|
||||
}
|
||||
|
||||
@PostMapping("/change-password")
|
||||
public ResponseEntity<?> changePassword(@RequestBody ChangePasswordRequest request) {
|
||||
if (authService.changePassword(request.getUsername(), request.getOldPassword(), request.getNewPassword())) {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
return ResponseEntity.badRequest().body("Change password failed");
|
||||
}
|
||||
}
|
||||
|
|
@ -2,17 +2,16 @@ package it.cnr.isti.workflow.manager.controllers;
|
|||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import it.cnr.isti.workflow.manager.blocks.Block;
|
||||
import it.cnr.isti.workflow.manager.blocks.configurations.BlockConfiguration;
|
||||
import it.cnr.isti.workflow.manager.blocks.factories.BlockFactory;
|
||||
import it.cnr.isti.workflow.manager.blocks.types.BlockType;
|
||||
import it.cnr.isti.workflow.manager.schema.JsonSchemaProducer;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
|
@ -29,22 +28,29 @@ public class BlocksController {
|
|||
@Autowired
|
||||
List<BlockFactory<?, ?>> blockFactories;
|
||||
|
||||
@GetMapping("")
|
||||
@Autowired
|
||||
JsonSchemaProducer schemaProducer;
|
||||
|
||||
@GetMapping("types")
|
||||
public List<BlockType> getTypes() {
|
||||
return new ArrayList<>(blockTypes.values());
|
||||
}
|
||||
|
||||
@GetMapping("/{type}/configuration")
|
||||
public String getConfigurations(@PathVariable String type) {
|
||||
BlockType blockType = blockTypes.get(type);
|
||||
if (blockType == null) {
|
||||
throw new IllegalArgumentException("Block type not found: " + type);
|
||||
}
|
||||
return blockType.getBlockConfigurationSchema();
|
||||
@GetMapping("/types/configurations/schemas")
|
||||
public List<String> getSchemaConfigurations() {
|
||||
return blockTypes.values().stream()
|
||||
.map(b -> schemaProducer.generateSchema(b.getBlockConfigurationClass()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@GetMapping("/types/{type}/configuration/schemas")
|
||||
public String getSchemaConfigurationForType(@PathVariable String type) {
|
||||
return schemaProducer.generateSchema(
|
||||
blockTypes.get(type).getBlockConfigurationClass());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@PostMapping()
|
||||
@PostMapping
|
||||
public <T extends BlockType, C extends BlockConfiguration<T>> Block<T, C> create(@RequestBody @Valid C blockConfiguration) {
|
||||
BlockFactory<T, C> factory = (BlockFactory<T, C>) blockFactories.stream()
|
||||
.filter(f -> f.getBlockType().equals(blockConfiguration.getType()))
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ import it.cnr.isti.workflow.manager.bricks.BrickManager;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package it.cnr.isti.workflow.manager.controllers;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import it.cnr.isti.workflow.manager.flows.repo.FlowEntity;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/flows")
|
||||
public class FlowController {
|
||||
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<FlowEntity> createFlow(@RequestBody FlowEntity flowEntity) {
|
||||
FlowEntity createdFlow = flowService.createFlow(flowEntity);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package it.cnr.isti.workflow.manager.flows;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import it.cnr.isti.workflow.manager.flows.model.Flow;
|
||||
import it.cnr.isti.workflow.manager.flows.repo.FlowEntity;
|
||||
import it.cnr.isti.workflow.manager.flows.repo.FlowRepository;
|
||||
|
||||
@Service
|
||||
public class FlowService {
|
||||
|
||||
@Autowired
|
||||
FlowRepository flowRepository;
|
||||
|
||||
public FlowEntity createFlow(String name, String description, String owner, Flow flow) {
|
||||
FlowEntity flowEntity = new FlowEntity();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
flowEntity.setCreatedAt(now);
|
||||
flowEntity.setLastUpdateAt(now);
|
||||
flowEntity.setFlow(flow);
|
||||
flowEntity.setOwner(owner);
|
||||
flowEntity.setName(name);
|
||||
flowEntity.setDescription(description);
|
||||
return flowRepository.save(flowEntity);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package it.cnr.isti.workflow.manager.flows;
|
||||
package it.cnr.isti.workflow.manager.flows.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package it.cnr.isti.workflow.manager.flows.repo;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import it.cnr.isti.workflow.manager.flows.model.Flow;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Lob;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
@Builder
|
||||
public class FlowEntity {
|
||||
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
@NotBlank
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@NotBlank
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime lastUpdateAt;
|
||||
|
||||
@NotBlank
|
||||
private String owner;
|
||||
|
||||
@Builder.Default
|
||||
private boolean published = false;
|
||||
|
||||
@Builder.Default
|
||||
private boolean finalized = false;
|
||||
|
||||
@Lob
|
||||
@Column(name = "flow_data", columnDefinition = "TEXT")
|
||||
private Flow flow;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package it.cnr.isti.workflow.manager.flows.repo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
|
||||
@Repository
|
||||
public interface FlowRepository extends JpaRepository<FlowEntity, String> {
|
||||
|
||||
@Query("SELECT f FROM FlowEntity f WHERE f.owner = :owner OR f.published = true")
|
||||
List<FlowEntity> findFlowsByOwnerOrPublic(@Param("owner") String owner);
|
||||
}
|
||||
|
|
@ -18,6 +18,5 @@ public @interface DynamicMultiValues {
|
|||
String message() default "value not valid";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
String key();
|
||||
String provider();
|
||||
String[] acceptedValues();
|
||||
}
|
||||
|
|
@ -21,14 +21,13 @@ public class DynamicSchemaGenerator {
|
|||
private final ObjectMapper objectMapper;
|
||||
private final JsonSchemaGenerator baseGenerator;
|
||||
|
||||
private Map<String, BrickManager<?>> brickManagers;
|
||||
|
||||
public DynamicSchemaGenerator(@Autowired ObjectMapper objectMapper, @Autowired Map<String, BrickManager<?>> brickManagers) {
|
||||
this.objectMapper = objectMapper;
|
||||
|
||||
public DynamicSchemaGenerator(@Autowired ObjectMapper objectMapper) {
|
||||
JsonSchemaConfig config = JsonSchemaConfig.vanillaJsonSchemaDraft4()
|
||||
.withFailOnUnknownProperties(false);
|
||||
this.baseGenerator = new JsonSchemaGenerator(objectMapper, config); // bean validation = true
|
||||
this.brickManagers = brickManagers;
|
||||
this.objectMapper = objectMapper;
|
||||
|
||||
}
|
||||
|
||||
public JsonNode generateJsonSchema(Class<?> clazz) {
|
||||
|
|
@ -38,15 +37,14 @@ public class DynamicSchemaGenerator {
|
|||
for (Field field : clazz.getDeclaredFields()) {
|
||||
DynamicMultiValues annotation = field.getAnnotation(DynamicMultiValues.class);
|
||||
if (annotation != null) {
|
||||
String key = annotation.key();
|
||||
String provider = annotation.provider();
|
||||
List<String> enumValues = brickManagers.get(provider).possibleValues(key);
|
||||
if (enumValues != null) {
|
||||
String[] acceptedValues = annotation.acceptedValues();
|
||||
|
||||
if (acceptedValues != null) {
|
||||
// Naviga nello schema e inietta "enum"
|
||||
JsonNode properties = schema.get("properties");
|
||||
if (properties != null && properties.has(field.getName())) {
|
||||
ArrayNode enumArray = objectMapper.createArrayNode();
|
||||
for (String value : enumValues) {
|
||||
for (String value : acceptedValues) {
|
||||
enumArray.add(value);
|
||||
}
|
||||
((com.fasterxml.jackson.databind.node.ObjectNode) properties.get(field.getName()))
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package it.cnr.isti.workflow.manager.schema;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
|
|
@ -15,8 +16,12 @@ public class JsonSchemaProducer {
|
|||
@Autowired
|
||||
private ObjectMapper mapper;
|
||||
|
||||
public String generateSchema(Class<?> clazz) throws Exception {
|
||||
public String generateSchema(Class<?> clazz) {
|
||||
JsonNode schema = schemaGen.generateJsonSchema(clazz);
|
||||
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema);
|
||||
try {
|
||||
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(schema);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException("Error generating JSON schema for class: " + clazz.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ import java.util.Map;
|
|||
@Component
|
||||
public class MultiValuesValidator implements ConstraintValidator<DynamicMultiValues, String> {
|
||||
|
||||
private String key;
|
||||
private String provider;
|
||||
private String[] acceptedValues;
|
||||
|
||||
private Map<String, BrickManager<?>> brickManagers;
|
||||
|
||||
|
|
@ -25,13 +24,11 @@ public class MultiValuesValidator implements ConstraintValidator<DynamicMultiVal
|
|||
|
||||
@Override
|
||||
public void initialize(DynamicMultiValues annotation) {
|
||||
this.key = annotation.key();
|
||||
this.provider = annotation.provider();
|
||||
this.acceptedValues = annotation.acceptedValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
List<String> allowed = brickManagers.get(provider).possibleValues(key);
|
||||
return value == null || allowed.contains(value);
|
||||
return value == null || List.of(acceptedValues).contains(value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,298 +0,0 @@
|
|||
[
|
||||
{
|
||||
"createdBy": "user.test",
|
||||
"name": "testFlow",
|
||||
"description": null,
|
||||
"nodes": [
|
||||
{
|
||||
"key": "e5f1f8d6-7717-4e32-ab57-f25f38f55401",
|
||||
"name": "newNode",
|
||||
"createdBy": null,
|
||||
"outputs": [
|
||||
{
|
||||
"key": "2967ca92-3977-44ad-8e19-318b9a71d227",
|
||||
"type": "text",
|
||||
"name": "value"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": -270,
|
||||
"y": -16
|
||||
},
|
||||
"parameters": {
|
||||
"name": "interview\n"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Text Input"
|
||||
},
|
||||
{
|
||||
"key": "1a4d1254-2c44-49fa-8aee-3865d1be9f95",
|
||||
"name": "newNode",
|
||||
"createdBy": null,
|
||||
"outputs": [
|
||||
{
|
||||
"key": "f36c7ae1-78da-4d71-8b44-58a1f87f17cb",
|
||||
"type": "text",
|
||||
"name": "requirements"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "7860945a-506b-45a3-97b6-cbf08023e421",
|
||||
"type": "text",
|
||||
"name": "interview"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": -11,
|
||||
"y": -23
|
||||
},
|
||||
"parameters": {
|
||||
"LLM": "llama3.1:8b"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Requirement Generator"
|
||||
},
|
||||
{
|
||||
"key": "394f3a84-f8b0-4cf6-92c7-042e6be03a26",
|
||||
"name": "newNode",
|
||||
"createdBy": null,
|
||||
"outputs": [
|
||||
{
|
||||
"key": "a73eb6d8-9010-428e-a0cd-0831fcba6c42",
|
||||
"type": "text",
|
||||
"name": "corrected-requirements"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "e83aebf3-05dd-4588-b9a3-03fd7253e5fc",
|
||||
"type": "text",
|
||||
"name": "requirement-smells"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": 321,
|
||||
"y": -21
|
||||
},
|
||||
"parameters": {
|
||||
"LLM": "gemma3:12b"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Requirements Corrector"
|
||||
},
|
||||
{
|
||||
"key": "511a2b09-dde6-4877-97ed-92819da4b778",
|
||||
"name": "newNode",
|
||||
"createdBy": null,
|
||||
"outputs": [
|
||||
{
|
||||
"key": "96750771-855b-4013-9959-ac8b3fd17133",
|
||||
"type": "text",
|
||||
"name": "requirement-smells"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "059dad32-a3ea-4a34-b77e-f747c2baf864",
|
||||
"type": "text",
|
||||
"name": "requirements"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": 707,
|
||||
"y": -22
|
||||
},
|
||||
"parameters": {
|
||||
"LLM": "codellama:latest"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Requirements Analyzer"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"key": "f8b4dbaa-a9cc-4470-89a2-7b999d905d9b",
|
||||
"from": "2967ca92-3977-44ad-8e19-318b9a71d227",
|
||||
"to": "7860945a-506b-45a3-97b6-cbf08023e421"
|
||||
},
|
||||
{
|
||||
"key": "9b2da750-f8c2-425a-a4c0-67869a60f69b",
|
||||
"from": "f36c7ae1-78da-4d71-8b44-58a1f87f17cb",
|
||||
"to": "e83aebf3-05dd-4588-b9a3-03fd7253e5fc"
|
||||
},
|
||||
{
|
||||
"key": "ec5ed95b-e546-4ee2-957b-039649f3fac4",
|
||||
"from": "a73eb6d8-9010-428e-a0cd-0831fcba6c42",
|
||||
"to": "059dad32-a3ea-4a34-b77e-f747c2baf864"
|
||||
}
|
||||
],
|
||||
"public": true
|
||||
},
|
||||
{
|
||||
"createdBy": "lucio.lelii",
|
||||
"name": "new flow",
|
||||
"description": null,
|
||||
"nodes": [
|
||||
{
|
||||
"key": "e5f1f8d6-7717-4e32-ab57-f25f38f55401",
|
||||
"name": "newNode",
|
||||
"createdBy": null,
|
||||
"outputs": [
|
||||
{
|
||||
"key": "2967ca92-3977-44ad-8e19-318b9a71d227",
|
||||
"type": "text",
|
||||
"name": "value"
|
||||
}
|
||||
],
|
||||
"inputs": [],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": -270,
|
||||
"y": -16
|
||||
},
|
||||
"parameters": {
|
||||
"name": "interview"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Text Input"
|
||||
},
|
||||
{
|
||||
"key": "1a4d1254-2c44-49fa-8aee-3865d1be9f95",
|
||||
"name": "newNode",
|
||||
"createdBy": null,
|
||||
"outputs": [
|
||||
{
|
||||
"key": "f36c7ae1-78da-4d71-8b44-58a1f87f17cb",
|
||||
"type": "text",
|
||||
"name": "requirements"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "7860945a-506b-45a3-97b6-cbf08023e421",
|
||||
"type": "text",
|
||||
"name": "interview"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": -11,
|
||||
"y": -23
|
||||
},
|
||||
"parameters": {
|
||||
"LLM": "llama3.1:8b"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Requirement Generator"
|
||||
},
|
||||
{
|
||||
"key": "394f3a84-f8b0-4cf6-92c7-042e6be03a26",
|
||||
"name": "newNode",
|
||||
"createdBy": null,
|
||||
"outputs": [
|
||||
{
|
||||
"key": "a73eb6d8-9010-428e-a0cd-0831fcba6c42",
|
||||
"type": "text",
|
||||
"name": "corrected-requirements"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "e83aebf3-05dd-4588-b9a3-03fd7253e5fc",
|
||||
"type": "text",
|
||||
"name": "requirement-smells"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": 321,
|
||||
"y": -21
|
||||
},
|
||||
"parameters": {
|
||||
"LLM": "gemma3:12b"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Requirements Corrector"
|
||||
},
|
||||
{
|
||||
"key": "511a2b09-dde6-4877-97ed-92819da4b778",
|
||||
"name": "newNode",
|
||||
"createdBy": null,
|
||||
"outputs": [
|
||||
{
|
||||
"key": "96750771-855b-4013-9959-ac8b3fd17133",
|
||||
"type": "text",
|
||||
"name": "requirement-smells"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "059dad32-a3ea-4a34-b77e-f747c2baf864",
|
||||
"type": "text",
|
||||
"name": "requirements"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": 707,
|
||||
"y": -22
|
||||
},
|
||||
"parameters": {
|
||||
"LLM": "codellama:latest"
|
||||
},
|
||||
"description": null,
|
||||
"type": "Requirements Analyzer"
|
||||
},
|
||||
{
|
||||
"key": "529bb583-05fe-4eb9-b194-1de65f577155",
|
||||
"name": "newNode",
|
||||
"createdBy": null,
|
||||
"outputs": [],
|
||||
"inputs": [
|
||||
{
|
||||
"key": "3677287f-e276-4869-913b-e985eedbfe08",
|
||||
"type": "text",
|
||||
"name": "value"
|
||||
}
|
||||
],
|
||||
"color": "#A8E6CF",
|
||||
"position": {
|
||||
"x": 1075,
|
||||
"y": 118
|
||||
},
|
||||
"parameters": null,
|
||||
"description": null,
|
||||
"type": "Text Output"
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
{
|
||||
"key": "f8b4dbaa-a9cc-4470-89a2-7b999d905d9b",
|
||||
"from": "2967ca92-3977-44ad-8e19-318b9a71d227",
|
||||
"to": "7860945a-506b-45a3-97b6-cbf08023e421"
|
||||
},
|
||||
{
|
||||
"key": "9b2da750-f8c2-425a-a4c0-67869a60f69b",
|
||||
"from": "f36c7ae1-78da-4d71-8b44-58a1f87f17cb",
|
||||
"to": "e83aebf3-05dd-4588-b9a3-03fd7253e5fc"
|
||||
},
|
||||
{
|
||||
"key": "ec5ed95b-e546-4ee2-957b-039649f3fac4",
|
||||
"from": "a73eb6d8-9010-428e-a0cd-0831fcba6c42",
|
||||
"to": "059dad32-a3ea-4a34-b77e-f747c2baf864"
|
||||
},
|
||||
{
|
||||
"key": "0aabf90f-d65d-4a21-8832-ad8ea6755547",
|
||||
"from": "96750771-855b-4013-9959-ac8b3fd17133",
|
||||
"to": "3677287f-e276-4869-913b-e985eedbfe08"
|
||||
}
|
||||
],
|
||||
"public": true
|
||||
}
|
||||
]
|
||||
|
|
@ -1,263 +0,0 @@
|
|||
[
|
||||
{
|
||||
"name": "Requirement Generator",
|
||||
"category": "Generators",
|
||||
"executor": "GENERIC-AI",
|
||||
"color": "black",
|
||||
"createdBy": "admin",
|
||||
"fixedParameters": {},
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"interview": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"requirements": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "requirements"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": {},
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"translation": "Analyze the following interview transcript and extract a list of functional requirements. \n\n${{interview}}\n\nFormat the output as a CSV with the following columns: \n1. **ID**: Unique identifier (e.g., F1, F2, F3…). \n2. **Requirement Text**: Clear description of what the system must do (in plain language). \n3. **Customer Quote**: Exact phrase from the transcript that justifies the requirement. \n\n**Rules:** \n- Include **only functional requirements** (no qualities or non-functional requirements). \n- Keep requirements concise and actionable. \n- List requirements in logical order.\n- Make sure requirements are free of smells.\n- Output only the CSV, do not add any text.\n\n**CSV Format Example:**\n\nF1; The system shall ask users to select the arithmetic operation to perform. The possible arithmetic operations are addition, subtraction, multiplication, division; “I need some support to perform different arithmetic operations”.\nF2: The system shall ask users to select the first operand of the operation; “I want to express the entire operation verbally, and get the results”.\nF3: The system shall ask users to select the second operand of the operation; “I want to express the entire operation verbally, and get the results”.\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Requirements Analyzer",
|
||||
"category": "Analyzers",
|
||||
"executor": "GENERIC-AI",
|
||||
"color": "black",
|
||||
"createdBy": "admin",
|
||||
"fixedParameters": {},
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"requirements": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"requirement-smells": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "requirement-smells"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": {},
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"translation": "giving the following requirements as csv:\n${{requirements}}\nAct as a **Requirements Analyst** tasked with reviewing a set of functional requirements derived from a customer interview. Analyze each requirement for potential **requirement smells** (e.g., ambiguity, incompleteness, inconsistency, or vagueness) and generate a CSV report with the following columns: \n\n1. **ID**: Unique identifier of the requirement (e.g., F1, F2). \n2. **Requirement**: The exact text of the requirement. \n3. **Customer Quote**: Supporting quote from the stakeholder. \n4. **Smell Description**: \n - If a smell is detected, briefly describe it (e.g., \"Ambiguous: 'user-friendly' is subjective\"). \n - If no smell is found, leave this field empty (null). \n\n**Rules:** \n- Read the requirement carefully, and think whether it can be tested\n- If it cannot be tested, understand what the smell is that makes it not testable. \n- Typical smells are: \nAmbiguity: Subjective terms (e.g., \"easy,\" \"fast\").\nIncompleteness: Missing steps/triggers (e.g., \"remind users\" but no timing specified).\nInconsistency: Conflicts with other requirements.\nVagueness: Unquantifiable metrics (e.g., \"often,\" \"sometimes\").\n- Output only the CSV, do not add any text.\n\n**Output CSV Format Example:** \n\nF1; The system shall ask users to select the arithmetic operation to perform. The possible arithmetic operations are addition, subtraction, multiplication, division; “I need some support to perform different arithmetic operations”; null\nF2; The system shall ask users to select the first operand of the operation; “I want to express the entire operation verbally, and get the results”; users is plural, can be ambiguous\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Requirements Corrector",
|
||||
"category": "Correctors",
|
||||
"executor": "GENERIC-AI",
|
||||
"color": "black",
|
||||
"createdBy": "admin",
|
||||
"fixedParameters": { },
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [ ],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"requirement-smells": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"corrected-requirements": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "corrected-requirements"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": { },
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"translation": "giving the input:\n\n${{requirement-smells}}\n\nthat is a CSV of requirements with identified smells (columns: `ID`, `Requirement`, `Customer Quote`, `Smell Description`). \n\nAct as a **Requirements Corrector**. Your task is to **revise smelly requirements** (ambiguity, vagueness, inconsistency, incompleteness) into **clear, actionable, and testable** functional requirements. \n\n**Output Rules:** \n- Generate a **new CSV** with columns: \n 1. **ID**: Original requirement identifier (e.g., `F1`, `F2`). \n 2. **Corrected Requirement**: Rewritten version that resolves the smell. \n 3. **Customer Quote**: Original supporting quote from the transcript. \n- **Remove the \"Smell Description\" column** (corrections should speak for themselves). \n- **Preserve intent**: Ensure the Corrected requirement aligns with the stakeholder’s original need. \n- **For non-smelly requirements**: Copy them verbatim to the new CSV.\n- Output only the CSV, do not add any text.\n\n**Output CSV Format Example:** \n\nF1; The system shall ask users to select the arithmetic operation to perform. The possible arithmetic operations are addition, subtraction, multiplication, division; “I need some support to perform different arithmetic operations”\nF2; The system shall ask the user to select the first operand of the operation; “I want to express the entire operation verbally, and get the results”;\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Class Modeller (UML)",
|
||||
"category": "Modellers",
|
||||
"executor": "GENERIC-AI",
|
||||
"color": "black",
|
||||
"createdBy": "admin",
|
||||
"fixedParameters": { },
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [ ],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"corrected-requirements": "Text",
|
||||
"interview-script": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"plant-uml": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "plant-uml"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": { },
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"translation": "Act as a **Requirements Modeler** specializing in structural design. Create a PlantUML class diagram that captures the core domain model implied by the functional requirements and interview transcript.\n\n**Input:** \n1. Corrected requirements (CSV with `ID`, `Corrected Requirement`, `Customer Quote`) :\n\n${{corrected-requirements}}\n \n2. Interview transcript for context :\n\n${{interview-script}}\n\n**Output Rules:** \n1. Generate **one concise class diagram** as a textual output in PlantUML syntax \n2. Focus on: \n - Key entities (classes) and their attributes \n - Critical relationships (associations, compositions) \n - Basic operations/methods implied by requirements \n3. Output only the model, do not add any text.\n\n**Example Output (PlantUML):**\n\n@startuml Calculator_Class_Diagram\n\nclass Calculator {\n + currentInput: Number\n + storedValue: Number\n + selectedOperation: OperationType\n + calculate(): Number\n + clear(): void\n}\n\nclass InputHandler {\n + handleDigitPress(digit: Integer): void\n + handleOperator(op: OperationType): void\n + handleEquals(): void\n}\n\nclass Display {\n + updateScreen(value: String): void\n}\n\nenum OperationType <<enum>> {\n ADD\n SUBTRACT\n MULTIPLY\n DIVIDE\n}\n\nCalculator \"1\" *-- \"1\" OperationType\nCalculator --> Display : updates\nInputHandler --> Calculator : commands\n\nnote top of Calculator : \"Must support basic arithmetic\\noperations (F2)\"\nnote right of InputHandler : \"User quote: 'I need buttons for\\nnumbers and functions...'\"\n\n@enduml\n"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "InputDispatcher",
|
||||
"category": "Dispachers",
|
||||
"executor": "NoOp",
|
||||
"color": "black",
|
||||
"createdBy": "admin",
|
||||
"fixedParameters": { },
|
||||
"runtimeParameters": { },
|
||||
"inputs": {
|
||||
"input": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"output": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"output": "output"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": {
|
||||
"input": "input"
|
||||
},
|
||||
"inputTranslators": { }
|
||||
},
|
||||
{
|
||||
"name": "Sequence Modeler",
|
||||
"category": "Modellers",
|
||||
"executor": "GENERIC-AI",
|
||||
"color": "black",
|
||||
"createdBy": "admin",
|
||||
"fixedParameters": { },
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [ ],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"interview": "Text",
|
||||
"plantuml-class-diagram": "Text",
|
||||
"corrected-requirements": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"plantuml-sequence-diagram": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "plantuml-sequence-diagram"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": { },
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"translation": "Act as a **Requirements Modeler** specializing in interaction design. Create a PlantUML sequence diagram that visually captures the key system interactions described in the functional requirements and interview transcript.\n\n**Input:**\n1. Cleaned requirements (CSV format with ID, Corrected Requirement, Customer Quote):\n\n${{corrected-requirements}}\n\n2. Original interview transcript for context:\n\n${{interview}}\n\n3. Class diagram:\n\n\n\n**Output Rules:**\n1. Generate **one comprehensive sequence diagram** in PlantUML syntax\n2. Consider only the classes from the input class diagram\n3. Focus on showing:\n - Primary user-to-system interactions\n - Critical system responses\n - Important timing constraints (where specified)\n4. Include only the most essential interactions (5-10 steps max)\n5. Output only the model, do not add any text.\n\n**Example Output (PlantUML):**\n\n@startuml Calculator_Sequence_Diagram \n\nactor User \nparticipant InputHandler \nparticipant Calculator \nparticipant Display \n\nUser -> InputHandler : Press digit \"5\" \nnote right of User: \"I need buttons for numbers...\" (Requirement F1) \nInputHandler -> Calculator : Store input (currentInput = 5) \nCalculator -> Display : Update screen (\"5\") \n\nUser -> InputHandler : Press operator \"+\" \nnote right of User: \"Basic arithmetic is essential\" (F3) \nInputHandler -> Calculator : Set operation (ADD) \n\nUser -> InputHandler : Press digit \"3\" \nInputHandler -> Calculator : Store input (currentInput = 3) \nCalculator -> Display : Update screen (\"3\") \n\nUser -> InputHandler : Press \"=\" \nInputHandler -> Calculator : calculate() \nCalculator -> Calculator : Compute 5 + 3 \nCalculator -> Display : Update screen (\"8\") \n\n@enduml \n"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Code Generator",
|
||||
"category": "Generators",
|
||||
"executor": "GENERIC-AI",
|
||||
"color": "black",
|
||||
"createdBy": "admin",
|
||||
"fixedParameters": { },
|
||||
"runtimeParameters": {
|
||||
"LLM": {
|
||||
"name": "LLM",
|
||||
"label": "Select the LLM to use",
|
||||
"description": "The LLM to use for generating requirements",
|
||||
"type": "Select",
|
||||
"required": true,
|
||||
"validations": [ ],
|
||||
"specificAttributes": {
|
||||
"options": [
|
||||
"google-gemini",
|
||||
"open-router"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"inputs": {
|
||||
"corrected-requirements": "Text",
|
||||
"class-diagram": "Text",
|
||||
"sequence-diagram": "Text"
|
||||
},
|
||||
"outputs": {
|
||||
"source-code": "Text"
|
||||
},
|
||||
"executorToOutputTranslationMappings": {
|
||||
"response": "source-code"
|
||||
},
|
||||
"inputToExecutorTranslationMappings": { },
|
||||
"inputTranslators": {
|
||||
"prompt": {
|
||||
"translation": "Act as a **Senior Software Engineer** specializing in **model-driven development**. Generate clean, maintainable code that:\n1. Implements all validated requirements (from CSV) \n2. Matches the structure of the provided class diagram \n3. Follows the interaction flows from the sequence diagram \n\n**Input:** \n1. requirements (ID, Corrected Requirement, Customer Quote):\n\n${{corrected-requirements}}\n\n2. class diagram (PlantUML) :\n\n${{class-diagram}}\n\n3. sequence diagram (PlantUML) :\n\n${{sequence-diagram}}\n\n**Output Rules:** \n- **Language**: Python\n- **Architecture**: \n - 1:1 mapping of PlantUML classes to code \n - Sequence diagram steps → method logic \n- **Quality**: \n - Type hints (Python)\n - Docstrings referencing requirement IDs (e.g., `# F1`) \n - Appropriate error handling \n- **Format**:\n - Output only executable code, do not add any text, besides comments\n\n**Example Output (Python Calculator):** \n\nfrom enum import Enum, auto\n\nclass OperationType(Enum):\n \"\"\"Supported operations (matches class diagram <<enum>>)\"\"\"\n ADD = auto() # F3: Basic arithmetic\n SUBTRACT = auto() # F3\n MULTIPLY = auto() # F3\n DIVIDE = auto() # F3\n\nclass Calculator:\n \"\"\"Core calculator logic (from class diagram)\"\"\"\n def __init__(self):\n self.current_input = 0 # F1: Digit entry\n self.stored_value = None # For multi-step operations\n self.selected_op = None # F3: Operation selection\n \n def calculate(self) -> float:\n \"\"\"Execute the current operation (sequence diagram '=' press)\"\"\"\n if not self.selected_op:\n return self.current_input\n \n # Sequence diagram compute step\n match self.selected_op:\n case OperationType.ADD:\n result = self.stored_value + self.current_input\n case OperationType.SUBTRACT:\n result = self.stored_value - self.current_input\n # ... other operations (F3)\n \n self.current_input = result\n return result\n\nclass InputHandler:\n \"\"\"Manages user input flow (sequence diagram interactions)\"\"\"\n def __init__(self, calculator: Calculator, display: Display):\n self.calc = calculator\n self.display = display\n \n def handle_digit(self, digit: int): # F1\n \"\"\"Process digit press (sequence diagram step 1)\"\"\"\n self.calc.current_input = self.calc.current_input * 10 + digit\n self.display.update(str(self.calc.current_input))\n\n def handle_operator(self, op: OperationType): # F3\n \"\"\"Process +,-,*,/ press (sequence diagram step 2)\"\"\"\n self.calc.stored_value = self.calc.current_input\n self.calc.selected_op = op\n self.calc.current_input = 0\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
[
|
||||
{
|
||||
"username": "testuser",
|
||||
"password": "testpassword"
|
||||
}
|
||||
]
|
||||
|
|
@ -4,35 +4,27 @@ import java.util.List;
|
|||
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
import it.cnr.isti.workflow.manager.bricks.Brick;
|
||||
import it.cnr.isti.workflow.manager.bricks.BrickManager;
|
||||
import it.cnr.isti.workflow.manager.bricks.LLMBrick;
|
||||
|
||||
@TestConfiguration
|
||||
@Profile("test")
|
||||
public class MyTestConfiguration {
|
||||
|
||||
@Bean(name = "brickTestManager")
|
||||
BrickManager<LLMBrick> brickManager() {
|
||||
return new BrickManager<LLMBrick>() {
|
||||
@Bean(name = "testProvider")
|
||||
BrickManager<Brick> brickManager() {
|
||||
return new BrickManager<Brick>() {
|
||||
|
||||
@Override
|
||||
public List<String> possibleValues(String key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LLMBrick> getBricks() {
|
||||
return List.of(new LLMBrick("llmProvider", "model", "provider"));
|
||||
public List<Brick> getBricks() {
|
||||
Brick b = new Brick("id", "testProvider") {
|
||||
};
|
||||
|
||||
return List.of(b);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
return http.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package it.cnr.isti.workflow.manager;
|
||||
|
||||
import org.springframework.boot.test.context.TestConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@TestConfiguration
|
||||
@Profile("test")
|
||||
public class TestSecurityConfig {
|
||||
|
||||
@Bean(name = "testSecurityFilterChain")
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
return http
|
||||
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,6 @@ public class TestConfig {
|
|||
|
||||
@Test
|
||||
public void generateJSONSchema() throws Exception {
|
||||
System.out.println(schemaProducer.generateSchema(LLMBrickConfiguration.class));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package it.cnr.isti.workflow.manager.controllers;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import it.cnr.isti.workflow.manager.auth.model.AuthRequest;
|
||||
|
||||
@SpringBootTest
|
||||
@TestPropertySource(locations = "classpath:test.properties")
|
||||
public class AuthControllerTest {
|
||||
|
||||
@Autowired
|
||||
private AuthController authController;
|
||||
|
||||
@Test
|
||||
public void testLogin() {
|
||||
ResponseEntity<?> response = authController.login(new AuthRequest("testuser", "testpassword"));
|
||||
assert response.getStatusCode().is2xxSuccessful();
|
||||
}
|
||||
}
|
||||
|
|
@ -8,16 +8,29 @@ import java.util.List;
|
|||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import it.cnr.isti.workflow.manager.MyTestConfiguration;
|
||||
import it.cnr.isti.workflow.manager.blocks.Block;
|
||||
import it.cnr.isti.workflow.manager.blocks.configurations.LLMBlockConfiguration;
|
||||
import it.cnr.isti.workflow.manager.blocks.types.BlockType;
|
||||
import it.cnr.isti.workflow.manager.blocks.types.LLMBlockType;
|
||||
import it.cnr.isti.workflow.manager.bricks.BrickManager;
|
||||
import it.cnr.isti.workflow.manager.bricks.LLMBrick;
|
||||
|
||||
@SpringBootTest
|
||||
@Import(MyTestConfiguration.class)
|
||||
@ActiveProfiles("test")
|
||||
public class BlocksControllerTest {
|
||||
|
||||
|
||||
@Qualifier("brickTestManager")
|
||||
@Autowired
|
||||
BrickManager<LLMBrick> llmBrickManager;
|
||||
|
||||
@Autowired
|
||||
private BlocksController blocksController;
|
||||
|
||||
|
|
@ -36,6 +49,7 @@ public class BlocksControllerTest {
|
|||
LLMBlockConfiguration config = LLMBlockConfiguration.builder()
|
||||
.prompt("Hello, ${{name}}!")
|
||||
.name("master")
|
||||
.brick(llmBrickManager.getBricks().get(0))
|
||||
.build();
|
||||
Block<LLMBlockType, LLMBlockConfiguration> block = blocksController.create(config);
|
||||
assertNotNull(block);
|
||||
|
|
|
|||
|
|
@ -2,29 +2,37 @@ package it.cnr.isti.workflow.manager.controllers;
|
|||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import it.cnr.isti.workflow.manager.MyTestConfiguration;
|
||||
import it.cnr.isti.workflow.manager.bricks.Brick;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
import java.util.List;
|
||||
|
||||
@SpringBootTest()
|
||||
@Import(MyTestConfiguration.class)
|
||||
@ActiveProfiles("test")
|
||||
public class BricksControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
private BricksController bricksController;
|
||||
|
||||
@Test
|
||||
public void testGetBricks() throws Exception {
|
||||
mockMvc.perform(get("/bricks/providers/testProvider"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(content().json("[{\"id\":\"provider:llmProvider_model\",\"provider\":\"llmProvider\",\"model\":\"model\"}]"));
|
||||
String provider = bricksController.getProviders().stream().filter(p -> p.equals("testProvider")).findFirst().orElse(null);
|
||||
assertNotNull(provider);
|
||||
assertEquals("testProvider", provider);
|
||||
List<? extends Brick> bricks = bricksController.getBricksByProvider(provider);
|
||||
assertTrue(!bricks.isEmpty());
|
||||
assertEquals(1, bricks.size());
|
||||
Brick brick = bricks.get(0);
|
||||
assertNotNull(brick);
|
||||
assertEquals("testProvider:id", brick.getId());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ spring.jpa.hibernate.ddl-auto=create-drop
|
|||
spring.jpa.properties.jakarta.persistence.validation.mode=none
|
||||
app.db.init.enabled=true
|
||||
|
||||
app.import.path=src/main/resources/workflow-editor-init
|
||||
app.import.enabled=false
|
||||
app.import.path=src/test/resources/workflow-editor-init
|
||||
app.import.enabled=true
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
[
|
||||
{
|
||||
"username": "testuser",
|
||||
"password": "testpassword"
|
||||
}
|
||||
]
|
||||
Loading…
Reference in New Issue