Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore/put requests validations #58

Merged
merged 4 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.llm_service.llm_service.constraint;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class TextLengthConstraintValidator implements ConstraintValidator<ValidTextLength, String> {

private int min;

@Override
public void initialize(ValidTextLength constraintAnnotation) {
this.min = constraintAnnotation.min();
}

@Override
public boolean isValid(String text, ConstraintValidatorContext context) {
if (text == null) {
return true;
}
if (text.length() < min) {
String errorMessage = "Text length must be at least " + min + " characters";
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(errorMessage).addConstraintViolation();
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.llm_service.llm_service.constraint;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class TitleConstraintValidator implements ConstraintValidator<ValidTitle, String> {

private int min;
private int max;

@Override
public void initialize(ValidTitle constraintAnnotation) {
this.min = constraintAnnotation.min();
this.max = constraintAnnotation.max();
}

@Override
public boolean isValid(String title, ConstraintValidatorContext context) {
if (title == null) {
return true;
}

int length = title.length();
if (length < min || length > max) {
String errorMessage = "Title length must be between " + min + " and " + max + " characters";
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(errorMessage).addConstraintViolation();
return false;
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.llm_service.llm_service.constraint;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Documented
@Constraint(validatedBy = TextLengthConstraintValidator.class)
@Target({FIELD})
@Retention(RUNTIME)
public @interface ValidTextLength {

String message() default "Text length must be at least {min} characters";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

int min() default 5;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.llm_service.llm_service.constraint;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Documented
@Constraint(validatedBy = TitleConstraintValidator.class)
@Target({FIELD})
@Retention(RUNTIME)
public @interface ValidTitle {

String message() default "Title length must be between {min} and {max} characters";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

int min() default 3;

int max() default 30;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@

import com.llm_service.llm_service.dto.Conversation;
import com.llm_service.llm_service.dto.Discussion;
import com.llm_service.llm_service.exception.UnAuthorizedException;
import com.llm_service.llm_service.dto.ValidationErrorResponse;
import com.llm_service.llm_service.exception.UnauthorizedException;
import com.llm_service.llm_service.exception.conversation.ConversationNotFoundException;
import com.llm_service.llm_service.service.ConversationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.Nullable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

@RestController
Expand All @@ -33,7 +39,7 @@ public class ConversationController {
})
@Operation(summary = "Get all conversations")
@GetMapping(value = "/api/v1/paid/conversation")
public ResponseEntity<List<ConversationResponseCompact>> getAllConversations() throws UnAuthorizedException {
public ResponseEntity<List<ConversationResponseCompact>> getAllConversations() throws UnauthorizedException {
return ResponseEntity.ok(conversationService.getAll().stream()
.map(conversationApiMapper::mapCompact)
.toList());
Expand All @@ -49,7 +55,7 @@ public ResponseEntity<List<ConversationResponseCompact>> getAllConversations() t
@Operation(summary = "Get conversation by ID")
@GetMapping("/api/v1/conversation/{id}")
public ResponseEntity<ConversationResponse> getConversationById(@PathVariable UUID id)
throws ConversationNotFoundException, UnAuthorizedException {
throws ConversationNotFoundException, UnauthorizedException {
Conversation conversation =
conversationService.getByID(id).orElseThrow(() -> new ConversationNotFoundException(id));

Expand Down Expand Up @@ -80,9 +86,13 @@ public ResponseEntity<ConversationResponse> createConversation() throws Exceptio
})
@Operation(summary = "Continue conversation using conversation ID")
@PutMapping("/api/v1/conversation/{id}/continue")
public ResponseEntity<List<DiscussionResponse>> continueConversation(
@PathVariable UUID id, @RequestBody ConversationRequest conversationRequest)
throws ConversationNotFoundException, UnAuthorizedException {
public ResponseEntity<?> continueConversation(
@PathVariable UUID id,
@Valid @RequestBody ConversationRequest conversationRequest,
BindingResult bindingResult)
throws ConversationNotFoundException, UnauthorizedException {
ResponseEntity<ValidationErrorResponse> errorResponse = getValidationErrorResponseResponseEntity(bindingResult);
if (errorResponse != null) return errorResponse;
Conversation conversation =
conversationService.getByID(id).orElseThrow(() -> new ConversationNotFoundException(id));
List<Discussion> discussions = conversationService.askLlmQuestion(conversation, conversationRequest.getText());
Expand All @@ -99,15 +109,34 @@ public ResponseEntity<List<DiscussionResponse>> continueConversation(
})
@Operation(summary = "update conversation title")
@PutMapping("/api/v1/conversation/{id}")
public ResponseEntity<ConversationResponseCompact> editConversation(
@PathVariable UUID id, @RequestBody ConversationTitleRequest conversationTitleRequest) throws Exception {
public ResponseEntity<?> editConversation(
@PathVariable UUID id,
@Valid @RequestBody ConversationTitleRequest conversationTitleRequest,
BindingResult bindingResult)
throws Exception {

ResponseEntity<ValidationErrorResponse> errorResponse = getValidationErrorResponseResponseEntity(bindingResult);
if (errorResponse != null) return errorResponse;

Conversation conversation =
conversationService.getByID(id).orElseThrow(() -> new ConversationNotFoundException(id));
conversation = conversationService.editTitle(conversation, conversationTitleRequest.getTitle());

return ResponseEntity.status(HttpStatus.OK).body(conversationApiMapper.mapCompact(conversation));
}

private static @Nullable ResponseEntity<ValidationErrorResponse> getValidationErrorResponseResponseEntity(
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
ValidationErrorResponse errorResponse = new ValidationErrorResponse(new HashMap<>());
for (FieldError error : bindingResult.getFieldErrors()) {
errorResponse.addError(error.getField(), error.getDefaultMessage());
}
return ResponseEntity.badRequest().body(errorResponse);
}
return null;
}

@ApiResponses(
value = {
@ApiResponse(
Expand All @@ -118,7 +147,7 @@ public ResponseEntity<ConversationResponseCompact> editConversation(
@Operation(summary = "deletes a conversation")
@DeleteMapping("/api/v1/conversation/{id}")
public ResponseEntity<Void> deleteConversation(@PathVariable UUID id)
throws ConversationNotFoundException, UnAuthorizedException {
throws ConversationNotFoundException, UnauthorizedException {
conversationService.getByID(id).orElseThrow(() -> new ConversationNotFoundException(id));
conversationService.delete(id);
return ResponseEntity.status(HttpStatus.OK).body(null);
Expand All @@ -144,8 +173,8 @@ public ResponseEntity<String> handleConversationNotFoundException(
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(conversationNotFoundException.getMessage());
}

@ExceptionHandler(UnAuthorizedException.class)
public ResponseEntity<String> handleUnAuthorized(UnAuthorizedException unAuthorizedException) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(unAuthorizedException.getMessage());
@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<String> handleUnAuthorized(UnauthorizedException unauthorizedException) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(unauthorizedException.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.llm_service.llm_service.controller.conversation;

import com.llm_service.llm_service.constraint.ValidTextLength;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
Expand All @@ -10,5 +11,6 @@
@Jacksonized
public class ConversationRequest {
@NonNull
@ValidTextLength
String text;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.llm_service.llm_service.controller.conversation;

import com.llm_service.llm_service.constraint.ValidTitle;
import lombok.Builder;
import lombok.NonNull;
import lombok.Value;
Expand All @@ -10,5 +11,6 @@
@Jacksonized
public class ConversationTitleRequest {
@NonNull
@ValidTitle
String title;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.llm_service.llm_service.controller.user;

import com.llm_service.llm_service.dto.User;
import com.llm_service.llm_service.exception.UnAuthorizedException;
import com.llm_service.llm_service.dto.ValidationErrorResponse;
import com.llm_service.llm_service.exception.UnauthorizedException;
import com.llm_service.llm_service.exception.user.UserAlreadyExistsException;
import com.llm_service.llm_service.exception.user.UserNotFoundException;
import com.llm_service.llm_service.exception.user.UsernameAlreadyExistsException;
Expand Down Expand Up @@ -39,8 +40,8 @@ public UserController(AuthenticationService authenticationService, UserApiMapper
})
@Operation(summary = "get the current user")
@GetMapping("/api/v1/me")
public ResponseEntity<UserResponse> register() throws UnAuthorizedException {
User user = authenticationService.getUser().orElseThrow(UnAuthorizedException::new);
public ResponseEntity<UserResponse> register() throws UnauthorizedException {
User user = authenticationService.getUser().orElseThrow(UnauthorizedException::new);
return ResponseEntity.status(HttpStatus.OK).body(userApiMapper.map(user));
}

Expand Down Expand Up @@ -102,4 +103,9 @@ ResponseEntity<String> handleUsernameNotFoundExceptions(UserNotFoundException us
ResponseEntity<String> handleAuthenticationException(AuthenticationException authenticationException) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(authenticationException.getMessage());
}

@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<String> handleUnauthorized(UnauthorizedException unauthorizedException) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(unauthorizedException.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.llm_service.llm_service.controller.user;
package com.llm_service.llm_service.dto;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.llm_service.llm_service.exception;

public class UnauthorizedException extends Exception {
public UnauthorizedException() {
super("UnAuthorized");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.llm_service.llm_service.dto.Conversation;
import com.llm_service.llm_service.dto.Discussion;
import com.llm_service.llm_service.dto.User;
import com.llm_service.llm_service.exception.UnAuthorizedException;
import com.llm_service.llm_service.exception.UnauthorizedException;
import com.llm_service.llm_service.persistance.entities.DiscussionRole;
import com.llm_service.llm_service.persistance.repositories.conversation.ConversationPersistenceManager;
import com.llm_service.llm_service.persistance.repositories.discussion.DiscussionPersistenceManager;
Expand All @@ -24,43 +24,43 @@ public class ConversationService {
private final DiscussionPersistenceManager discussionPersistenceManager;
private final UserContext userContext;

public Conversation start() throws UnAuthorizedException {
public Conversation start() throws UnauthorizedException {
Optional<User> user = userContext.getUserFromContext();

if (user.isEmpty()) {
throw new UnAuthorizedException();
throw new UnauthorizedException();
}

Conversation conversation = Conversation.builder().discussions(null).build();
return conversationPersistenceManager.save(conversation, user.get());
}

public List<Conversation> getAll() throws UnAuthorizedException {
public List<Conversation> getAll() throws UnauthorizedException {
Optional<User> user = userContext.getUserFromContext();

if (user.isEmpty()) {
throw new UnAuthorizedException();
throw new UnauthorizedException();
}

return conversationPersistenceManager.findAll(user.get().getId());
}

public Optional<Conversation> getByID(UUID id) throws UnAuthorizedException {
public Optional<Conversation> getByID(UUID id) throws UnauthorizedException {
Optional<User> user = userContext.getUserFromContext();
if (user.isEmpty()) {
throw new UnAuthorizedException();
throw new UnauthorizedException();
}

return conversationPersistenceManager.findById(id, user.get().getId());
}

// TODO optimize this fetching mechanism
public List<Discussion> askLlmQuestion(Conversation conversation, String text) throws UnAuthorizedException {
public List<Discussion> askLlmQuestion(Conversation conversation, String text) throws UnauthorizedException {

Optional<User> user = userContext.getUserFromContext();

if (user.isEmpty()) {
throw new UnAuthorizedException();
throw new UnauthorizedException();
}

Discussion discussionFromUserParam =
Expand Down Expand Up @@ -106,11 +106,11 @@ public void deleteAll() {
conversationPersistenceManager.deleteAll();
}

public Conversation editTitle(Conversation conversation, String title) throws UnAuthorizedException {
public Conversation editTitle(Conversation conversation, String title) throws UnauthorizedException {
Optional<User> user = userContext.getUserFromContext();

if (user.isEmpty()) {
throw new UnAuthorizedException();
throw new UnauthorizedException();
}
Conversation savedConversation = saveEditedTitle(conversation, title, user);

Expand Down