Skip to content

Commit

Permalink
merge dev
Browse files Browse the repository at this point in the history
  • Loading branch information
holotsvan committed Dec 9, 2024
2 parents f6c4047 + 4da954f commit 17bec6c
Show file tree
Hide file tree
Showing 42 changed files with 847 additions and 175 deletions.
4 changes: 4 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@
<artifactId>jsoup</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/java/greencity/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
FRIENDS + "/user/{userId}",
"/habit/assign/confirm/{habitAssignId}",
"/database/backup",
"/database/backupFiles")
"/database/backupFiles",
"/ai/**")
.permitAll()
.requestMatchers(HttpMethod.POST,
SUBSCRIPTIONS,
Expand Down
46 changes: 46 additions & 0 deletions core/src/main/java/greencity/controller/AIController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package greencity.controller;

import greencity.annotations.ApiLocale;
import greencity.annotations.CurrentUser;
import greencity.constant.HttpStatuses;
import greencity.dto.user.UserVO;
import greencity.service.AIService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Locale;

@RestController
@RequestMapping("/ai")
@AllArgsConstructor
public class AIController {
private final AIService aiService;

@Operation(summary = "Makes predictions about the environmental impact of the current user "
+ "based on the analysis of their habits and habit duration.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = HttpStatuses.OK),
@ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST,
content = @Content(examples = @ExampleObject(HttpStatuses.BAD_REQUEST))),
@ApiResponse(responseCode = "401", description = HttpStatuses.UNAUTHORIZED,
content = @Content(examples = @ExampleObject(HttpStatuses.UNAUTHORIZED))),
@ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND,
content = @Content(examples = @ExampleObject(HttpStatuses.NOT_FOUND)))
})
@ApiLocale
@GetMapping("/forecast")
public ResponseEntity<String> forecast(@Parameter(hidden = true) @CurrentUser UserVO userVO,
@Parameter(hidden = true) Locale locale) {
return ResponseEntity.status(HttpStatus.OK)
.body(aiService.getForecast(userVO.getId(), locale.getDisplayLanguage()));
}
}
6 changes: 5 additions & 1 deletion core/src/main/java/greencity/controller/EventController.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,11 @@ public ResponseEntity<EventDto> getEvent(
public ResponseEntity<PageableAdvancedDto<EventDto>> getEvents(
@Parameter(hidden = true) Pageable pageable,
@RequestParam(required = false, name = "user-id") Long userId,
FilterEventDto filterEventDto) {
@Schema(
description = "Filters for events",
name = "FilterEventDto",
type = "object",
example = FilterEventDto.defaultJson) FilterEventDto filterEventDto) {
if (filterEventDto != null && filterEventDto.getStatuses() != null) {
validateStatusesRequireUserId(filterEventDto.getStatuses(), userId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,8 @@ public ResponseEntity<List<Long>> deleteAll(@RequestBody List<Long> listId) {
@ApiResponse(responseCode = "403", description = HttpStatuses.FORBIDDEN)
})
@PatchMapping("/switch-deleted-status/{id}")
public ResponseEntity<Long> switchIsDeletedStatus(@PathVariable("id") Long id) {
managementHabitService.switchIsDeletedStatus(id);
public ResponseEntity<Long> switchIsDeletedStatus(@PathVariable("id") Long id, @RequestBody Boolean newStatus) {
managementHabitService.switchIsDeletedStatus(id, newStatus);
return ResponseEntity.status(HttpStatus.OK).body(id);
}
}
6 changes: 5 additions & 1 deletion core/src/main/resources/application-dev.properties
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,8 @@ pg.dump.path=pg_dump
google.maps.api.key=${GOOGLE_MAP_API_KEY:AIzaSyCU0ArzZlZ3n0pLq4o9MJy29LPT5DBMk4Y}

# Cron
cron.sendContentToSubscribers=${CRON_SEND_CONTENT_TO_SUBSCRIBERS:0 0 20 * * SAT}
cron.sendContentToSubscribers=${CRON_SEND_CONTENT_TO_SUBSCRIBERS:0 0 20 * * SAT}

# OpenAI
openai.api.key=${OPEN_AI_API_KEY:sk-proj-nCJktK9QTYEogyNa6mGghwftDa4E6kA}
openai.api.url=https://api.openai.com/v1/chat/completions
6 changes: 5 additions & 1 deletion core/src/main/resources/application-docker.properties
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,8 @@ azure.container.name=${AZURE_CONTAINER_NAME}
pg.dump.path=pg_dump

# Cron
cron.sendContentToSubscribers=${CRON_SEND_CONTENT_TO_SUBSCRIBERS:0 0 20 * * SAT}
cron.sendContentToSubscribers=${CRON_SEND_CONTENT_TO_SUBSCRIBERS:0 0 20 * * SAT}

# OpenAI
openai.api.key=${OPEN_AI_API_KEY:sk-proj-nCJktK9QTYEogyNa6mGghwftDa4E6kA}
openai.api.url=https://api.openai.com/v1/chat/completions
6 changes: 5 additions & 1 deletion core/src/main/resources/application-test.properties
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ azure.container.name=test
pg.dump.path=pg_dump

# Cron
cron.sendContentToSubscribers=${CRON_SEND_CONTENT_TO_SUBSCRIBERS:0 0 20 * * SAT}
cron.sendContentToSubscribers=${CRON_SEND_CONTENT_TO_SUBSCRIBERS:0 0 20 * * SAT}

# OpenAI
openai.api.key=${OPEN_AI_API_KEY:sk-proj-nCJktK9QTYEogyNa6mGghwftDa4E6kA}
openai.api.url=https://api.openai.com/v1/chat/completions
4 changes: 4 additions & 0 deletions core/src/main/resources/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ greenCity.pages.table.startDate=Start date
greenCity.pages.table.endDate=End date
greenCity.pages.table.ready=Ready
greenCity.pages.table.location=Location
greenCity.pages.table.isCustomHabit=Custom Habit
greenCity.pages.table.isDeleted=Deleted (Hidden)
greenCity.pages.yes=true
greenCity.pages.no=false
greenCity.pages.popup.delete.all.h=Delete all selected
greenCity.pages.popup.delete.all.h1=Are you sure you want to delete these Records?
greenCity.pages.popup.delete.all.h2=This action cannot be undone.
Expand Down
58 changes: 58 additions & 0 deletions core/src/main/resources/templates/core/management_user_habits.html
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ <h2 class="text-describe">[[#{greenCity.habit.page.h}]]</h2>
</th>
<th>[[#{greenCity.pages.table.description}]]
</th>
<th>[[#{greenCity.pages.table.isCustomHabit}]]</th>
<th>[[#{greenCity.pages.table.isDeleted}]]</th>
<th>
[[#{greenCity.pages.table.actions}]]
</th>
Expand Down Expand Up @@ -242,6 +244,15 @@ <h2 class="text-describe">[[#{greenCity.habit.page.h}]]</h2>
<td>
<div class="pr" style="width: 27em">[[${translation.description}]]</div>
</td>
<td class="habit_isCustomHabit" th:text="${habit.isCustomHabit}"></td>
<td class="habit_isDeleted">
<select th:id="'status-'+${habit.id}" th:onchange="'toggleStatus('+${habit.id}+');'" class="form-control">
<option th:if="${habit.isDeleted == null}" th:value="null" th:selected="true">Not Set</option>
<option th:value="true" th:selected="${habit.isDeleted == true}">True</option>
<option th:value="false" th:selected="${habit.isDeleted == false}">False</option>
</select>
</td>

<td>
<a href="#editHabitModal" class="restore edit eBtn" th:data-id="${habit.id}" data-toggle="modal">
<i class="material-icons" data-toggle="tooltip" th:title="#{greenCity.pages.edit}">&#xE254;</i>
Expand Down Expand Up @@ -853,6 +864,53 @@ <h4 class="modal-title">[[#{greenCity.pages.popup.delete.all.h}]]</h4>
});
});
</script>
<script th:inline="javascript">
function toggleStatus(habitId) {
const selectElement = document.getElementById('status-' + habitId);
const selectedValue = selectElement.value;

if (selectedValue === "null") {
alert('You must select a valid status (True or False)');
return;
}

const newStatus = selectedValue === 'true';

fetch(`/management/habits/switch-deleted-status/${habitId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newStatus)
})
.then(response => {
if (response.ok) {
updateDropdown(habitId, newStatus);
alert('Status updated successfully');
} else {
alert('Failed to update status');
}
})
.catch(error => {
console.error('Error updating status:', error);
alert('An error occurred while updating the status');
});
}

function updateDropdown(habitId, newStatus) {
const selectElement = document.getElementById('status-' + habitId);

selectElement.innerHTML = '';

if (newStatus) {
selectElement.innerHTML += `<option value="true" selected>True</option>`;
selectElement.innerHTML += `<option value="false">False</option>`;
} else {
selectElement.innerHTML += `<option value="false" selected>False</option>`;
selectElement.innerHTML += `<option value="true">True</option>`;
}
}
</script>
</body>

</html>
19 changes: 0 additions & 19 deletions core/src/test/java/greencity/IntegrationTestBase.java

This file was deleted.

55 changes: 55 additions & 0 deletions core/src/test/java/greencity/controller/AIControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package greencity.controller;

import greencity.service.AIService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.context.annotation.Import;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import java.security.Principal;
import java.util.Locale;
import static greencity.ModelUtils.getPrincipal;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@Import(AIController.class)
class AIControllerTest {
@Mock
private AIService aiService;
@InjectMocks
private AIController aiController;
private MockMvc mockMvc;
private Principal principal = getPrincipal();

@BeforeEach
void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(aiController)
.setCustomArgumentResolvers(new PageableHandlerMethodArgumentResolver())
.build();
}

@Test
void forecast_ReturnsForecast_FromAIService() throws Exception {
Locale testLocale = Locale.forLanguageTag("en");

mockMvc.perform(get("/ai/forecast")
.principal(principal)
.locale(testLocale))
.andExpect(status().isOk());

verify(aiService, times(1)).getForecast(any(), eq(testLocale.getDisplayLanguage()));
}
}
35 changes: 0 additions & 35 deletions core/src/test/java/greencity/repository/LanguageRepoTest.java

This file was deleted.

21 changes: 0 additions & 21 deletions core/src/test/java/greencity/repository/ModelUtils.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,13 @@ void deleteAll() throws Exception {
@Test
void switchIsDeletedStatusTest() throws Exception {
Long habitId = 1L;
this.mockMvc.perform(MockMvcRequestBuilders.patch(habitManagementLink + "/switch-deleted-status/" + habitId))
Boolean newStatus = true;

this.mockMvc.perform(MockMvcRequestBuilders.patch(habitManagementLink + "/switch-deleted-status/" + habitId)
.contentType(MediaType.APPLICATION_JSON)
.content(newStatus.toString()))
.andExpect(status().isOk());

verify(managementHabitService).switchIsDeletedStatus(habitId);
verify(managementHabitService).switchIsDeletedStatus(habitId, newStatus);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ public class ErrorMessage {
public static final String USER_HAS_NO_FRIEND_WITH_ID = "User has no friend with this id: ";
public static final String INVALID_DURATION = "The duration for such habit is lower than previously set";
public static final String ADDRESS_NOT_FOUND_EXCEPTION = "No address found for the given coordinates.";
public static final String INVALID_COORDINATES = "The coordinates field must not be empty";
public static final String INVALID_LONGITUDE = "Longitude must be between -180 and 180 degrees";
public static final String INVALID_LATITUDE = "Latitude must be between -90 and 90 degrees";
public static final String INVALID_DATE = "Date can't be null or empty";
public static final String NO_FRIENDS_ASSIGNED_ON_CURRENT_HABIT =
"No friends are assigned on current habit with id: ";
Expand Down Expand Up @@ -211,4 +214,6 @@ public class ErrorMessage {
public static final String YOU_HAS_ALREADY_ACCEPT_THIS_INVITATION = "Current user already has accepted invitation";
public static final String INVITATION_ALREADY_EXIST = "Invitation already exist";
public static final String INVALID_DURATION_BETWEEN_START_AND_FINISH = "Invalid duration between start and finish";
public static final String PAGE_NOT_FOUND_MESSAGE = "Requested page %d exceeds total pages %d.";
public static final String OPEN_AI_IS_NOT_RESPONDING = "Could not get a response from OpenAI.";
}
11 changes: 11 additions & 0 deletions service-api/src/main/java/greencity/constant/OpenAIRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package greencity.constant;

import lombok.experimental.UtilityClass;

@UtilityClass
public class OpenAIRequest {
public static final String FORECAST =
"respond in a personalized manner, concisely, and accurately. Use numbers to present approximate data. "
+ "Provide a forecast of this person's impact on the global environment, considering their habits "
+ "over the specified number of days:";
}
Loading

0 comments on commit 17bec6c

Please sign in to comment.