Skip to content

Commit

Permalink
Feature: manage ai endpoint (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zorin95670 authored Aug 28, 2024
2 parents 95f9a06 + 9ccc603 commit 351b318
Show file tree
Hide file tree
Showing 24 changed files with 536 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
- name: Build application
run: docker build -t leto-modelizer-api .
- name: Run application
run: docker compose up -d
run: docker compose -f docker-compose-e2e.yml up -d
env:
GITHUB_CLIENT_ID: test
GITHUB_CLIENT_SECRET: test
Expand Down
11 changes: 0 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,6 @@ To run all the application tests (unit and integration), use this command:
./gradlew test -i
```

To run only the application unit tests, use this command:

```shell
./gradlew unitTest -i
```

To run all the application integration tests, use this command:

```shell
./gradlew integrationTest -i
```
## Checkstyle

Before pushing your branch and open/synchronize a pull-request, you have to verify the checkstyle of your application. Here is the command to do so:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ enabling secure and streamlined user authentication.
| CSRF_TOKEN_TIMEOUT | No, default: `3600` | A configuration parameter that specifies the duration (in seconds) for which a Cross-Site Request Forgery (CSRF) token remains valid. This setting is used to prevent CSRF attacks by ensuring that the token used in a client session expires after a certain period, requiring a new token for future requests. |
| USER_SESSION_TIMEOUT | No, default: `3600` | A configuration parameter that defines the time (in seconds) a user's session remains active without any activity. After this period, the user is automatically logged out to help protect against unauthorized access and to manage server resource utilization efficiently. |
| SUPER_ADMINISTRATOR_LOGIN | No | A configuration parameter that defines the username on Github of the SUPER_ADMINISTRATOR. It will create user if it doesn't exist and associate it to the `SUPER_ADMINISTRATOR` role. |
| AI_HOST | No | A configuration parameter that defines the host of the ia server, example: http://localhost:8001/api/. If it's not set, users will not be approve to use ia in application. |

> Notes: `GITHUB_ENTERPRISE_*` variables are only required on self-hosted GitHub.
Expand Down Expand Up @@ -274,6 +275,7 @@ LETO_ADMIN_URL=http://localhost:9000/
LIBRARY_HOST_WHITELIST=https://github.com/ditrit/
CSRF_TOKEN_TIMEOUT=3600
USER_SESSION_TIMEOUT=3600
AI_HOST=http://locahost:8001/api/
```

See Configuration section for more details.
Expand Down
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* `GET /api/libraries/templates/[TEMPLATE_ID]/icon`, to get icon of template.
* `GET /api/libraries/templates/[TEMPLATE_ID]/schemas/[INDEX]`, to get schema of template.
* `GET /api/libraries/templates/[TEMPLATE_ID]/files/[INDEX]`, to get file of template.
* For AI:
* `GET /api/ia`, to create diagram with AI.
* `/api/login`, to login.
* `/api/redirect`, to redirect with token on leto-modelizer/leto-modelizer-admin.
54 changes: 54 additions & 0 deletions docker-compose-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
services:
libraries:
image: nginx:latest
ports:
- "8000:80"
volumes:
- ./src/test/resources/libraries:/usr/share/nginx/html:ro
- ./nginx_lib_dev.conf:/etc/nginx/conf.d/default.conf:ro
restart: always
ai:
build: ./src/test/resources/ai
ports:
- "8001:80"
volumes:
- ./src/test/resources/ai:/var/www/html
restart: always
db:
image: cockroachdb/cockroach:v23.1.15
command: start-single-node --certs-dir=/certs --advertise-addr=db
volumes:
- ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
- ./cockroach-certs:/certs
ports:
- "26257:26257"
- "8081:8080"
restart: always
api:
restart: always
image: leto-modelizer-api:latest
environment:
DATABASE_HOST: db:26257
AI_HOST: http://ai/api/
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
LIBRARY_HOST_WHITELIST: http://libraries/
SUPER_ADMINISTRATOR_LOGIN: ${SUPER_ADMINISTRATOR_LOGIN}
POSTGRES_DB: ${POSTGRES_DB:-leto_db}
POSTGRES_USER: ${POSTGRES_USER:-leto_admin}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
DATABASE_NAME: ${DATABASE_NAME:-leto_db}
DATABASE_USER: ${DATABASE_USER:-leto_admin}
DATABASE_PASSWORD: ${DATABASE_PASSWORD:-password}
SSL_KEY_STORE: ${SSL_KEY_STORE:-classpath:keystore.jks}
SSL_KEY_STORE_PASSWORD: ${SSL_KEY_STORE_PASSWORD:-password}
SSL_KEY_PASSWORD: ${SSL_KEY_PASSWORD:-password}
LETO_MODELIZER_URL: ${LETO_MODELIZER_URL:-http://localhost:8080/}
LETO_ADMIN_URL: ${LETO_ADMIN_URL:-http://localhost:9000/}
CSRF_TOKEN_TIMEOUT: ${CSRF_TOKEN_TIMEOUT:-3600}
USER_SESSION_TIMEOUT: ${USER_SESSION_TIMEOUT:-3600}

ports:
- "8443:8443"
depends_on:
- db
26 changes: 9 additions & 17 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ services:
- "8000:80"
volumes:
- ./src/test/resources/libraries:/usr/share/nginx/html:ro
- ./nginx_dev.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx_lib_dev.conf:/etc/nginx/conf.d/default.conf:ro
restart: always
ai:
image: leto-modelizer-ai-proxy:latest
ports:
- "8585:8585"
network_mode: "host"
restart: always
db:
image: cockroachdb/cockroach:v23.1.15
Expand All @@ -21,26 +27,12 @@ services:
restart: always
image: leto-modelizer-api:latest
environment:
POSTGRES_DB: ${POSTGRES_DB:-leto_db}
POSTGRES_USER: ${POSTGRES_USER:-leto_admin}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
DATABASE_HOST: db:26257
DATABASE_NAME: ${DATABASE_NAME:-leto_db}
DATABASE_USER: ${DATABASE_USER:-leto_admin}
DATABASE_PASSWORD: ${DATABASE_PASSWORD:-password}
SSL_KEY_STORE: ${SSL_KEY_STORE:-classpath:keystore.jks}
SSL_KEY_STORE_PASSWORD: ${SSL_KEY_STORE_PASSWORD:-password}
SSL_KEY_PASSWORD: ${SSL_KEY_PASSWORD:-password}
AI_HOST: http://ai/api/
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
LIBRARY_HOST_WHITELIST: http://libraries/
SUPER_ADMINISTRATOR_LOGIN: ${SUPER_ADMINISTRATOR_LOGIN}
LETO_MODELIZER_URL: ${LETO_MODELIZER_URL:-http://localhost:8080/}
LETO_ADMIN_URL: ${LETO_ADMIN_URL:-http://localhost:9000/}
LIBRARY_HOST_WHITELIST: ${LIBRARY_HOST_WHITELIST:-http://libraries/}
CSRF_TOKEN_TIMEOUT: ${CSRF_TOKEN_TIMEOUT:-3600}
USER_SESSION_TIMEOUT: ${USER_SESSION_TIMEOUT:-3600}


ports:
- "8443:8443"
depends_on:
Expand Down
1 change: 1 addition & 0 deletions nginx_dev.conf → nginx_lib_dev.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Nginx configuration to simulate storage server for libraries.
server {
listen 80;
server_name localhost;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.ditrit.letomodelizerapi.controller.CurrentUserController;
import com.ditrit.letomodelizerapi.controller.GroupController;
import com.ditrit.letomodelizerapi.controller.HomeController;
import com.ditrit.letomodelizerapi.controller.AIController;
import com.ditrit.letomodelizerapi.controller.LibraryController;
import com.ditrit.letomodelizerapi.controller.PermissionController;
import com.ditrit.letomodelizerapi.controller.RoleController;
Expand All @@ -13,7 +14,10 @@
import com.ditrit.letomodelizerapi.controller.handler.ConstraintViolationExceptionHandler;
import com.ditrit.letomodelizerapi.controller.handler.DataIntegrityViolationExceptionHandler;
import com.ditrit.letomodelizerapi.controller.handler.IllegalArgumentExceptionHandler;
import org.apache.commons.lang.StringUtils;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
Expand All @@ -24,8 +28,10 @@ public class JerseyConfig extends ResourceConfig {

/**
* Default constructor to initialize registered endpoints and filters.
* @param aiHost the host value for ai request, injected from application properties.
*/
public JerseyConfig() {
@Autowired
public JerseyConfig(@Value("${ai.host}") final String aiHost) {
// Filter
// Controller
register(UserController.class);
Expand All @@ -38,6 +44,10 @@ public JerseyConfig() {
register(CsrfController.class);
register(PermissionController.class);

if (StringUtils.isNotBlank(aiHost)) {
register(AIController.class);
}

// Exception handler
register(ApiExceptionHandler.class);
register(ConstraintViolationExceptionHandler.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.ditrit.letomodelizerapi.controller;

import com.ditrit.letomodelizerapi.model.ai.AIRequestRecord;
import com.ditrit.letomodelizerapi.persistence.model.User;
import com.ditrit.letomodelizerapi.service.AIService;
import com.ditrit.letomodelizerapi.service.UserService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;

/**
* Controller to manage ai endpoint.
*/
@Path("/ai")
@Produces(MediaType.APPLICATION_JSON)
@Controller
@Slf4j
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class AIController {

/**
* Service to manage user.
*/
private UserService userService;

/**
* Service to manage ai request.
*/
private AIService aiService;

/**
* Handles a POST request to initiate an interaction with an Artificial Intelligence (AI) based on the provided
* request details.
* This endpoint accepts an AI request record, which includes the parameters necessary for the AI interaction.
* The method retrieves the user from the session, logs the request details, and forwards the request to the AI
* service. It then constructs and returns a response containing the AI's output.
*
* <p>The method uses the AI service to process the request by the user, generating a JSON response that is returned
* to the client.
* This process allows for dynamic interactions with the AI, facilitating use cases such as querying for
* information, executing commands, or initiating workflows within the application.
*
* @param request the HttpServletRequest, used to access the user's session.
* @param aiRequestRecord the request details for the AI, validated to ensure it meets the expected format.
* @return a Response object containing the AI's response in JSON format, with a status of OK (200).
*/

@POST
public Response requestAI(final @Context HttpServletRequest request,
final @Valid AIRequestRecord aiRequestRecord) throws InterruptedException {
HttpSession session = request.getSession();
User user = userService.getFromSession(session);

log.info("[{}] Received POST request to request AI with {}", user.getLogin(), aiRequestRecord);

String json = aiService.sendRequest(aiRequestRecord);

return Response.status(HttpStatus.CREATED.value())
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.entity(json)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.ditrit.letomodelizerapi.model.ai;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;

/**
* A record representing a request to an Artificial Intelligence (AI) within the application.
* This record includes details about the request such as the plugin involved, the type of request, and
* a description of the request. It is used as a data transfer object to encapsulate the information needed
* by the AI to process user requests.
*
* @param plugin the name of the plugin related to the AI request. Must not be blank.
* @param type the type of the AI request, constrained to specific values such as "diagram" by a pattern
* to ensure that only valid request types are considered.
* @param description a description of the AI request, providing context or additional information. Must not be blank.
*/
public record AIRequestRecord(
@NotBlank String plugin,
@Pattern(regexp = "diagram") String type,
@NotBlank String description
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Package that contains ai models.
*/
package com.ditrit.letomodelizerapi.model.ai;
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ public enum ErrorType {
/**
* Error to call when a file of library can't be downloaded.
*/
LIBRARY_FILE_DOWNLOAD_ERROR(211, "Library file can't be downloaded", HttpStatus.BAD_REQUEST);
LIBRARY_FILE_DOWNLOAD_ERROR(211, "Library file can't be downloaded", HttpStatus.BAD_REQUEST),

/**
* Error to use when the AI proxy fails to generate code.
*/
AI_GENERATION_ERROR(530, "The AI could not generate a proper response", HttpStatus.INTERNAL_SERVER_ERROR);

/**
* Error code.
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/com/ditrit/letomodelizerapi/service/AIService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.ditrit.letomodelizerapi.service;

import com.ditrit.letomodelizerapi.model.ai.AIRequestRecord;

/**
* Service implementation for interacting with an Artificial Intelligence (AI).
* This class provides concrete implementation for sending requests to an AI, based on user input and specific
* request details encapsulated in an AIRequestRecord.
*/
public interface AIService {

/**
* Sends a request to the Artificial Intelligence (AI) based on the provided user and request record.
* This method constructs the request, sends it to the AI system, and returns the AI response as a String.
*
* @param aiRequest the AIRequestRecord containing details about the request to be sent to the AI.
* @return the response from the AI as a String.
*/
String sendRequest(AIRequestRecord aiRequest);
}
Loading

0 comments on commit 351b318

Please sign in to comment.