Skip to content

Commit

Permalink
Merge pull request #7258 from ita-social-projects/backup-feature
Browse files Browse the repository at this point in the history
  • Loading branch information
KizerovDmitriy committed Jul 9, 2024
2 parents 4d1182a + 7a1600d commit 13394f5
Show file tree
Hide file tree
Showing 16 changed files with 540 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
git fetch --unshallow
mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar -Dsonar.projectKey=ita-social-projects-green-city -Dsonar.organization=ita-social-projects -Dsonar.host.url=https://sonarcloud.io -Dsonar.binaries=target/classes -Dsonar.dynamicAnalysis=reuseReports -Dsonar.coverage.exclusions=**/config/*,**/entity/*,**/entity/**,**/exceptions/*,**/exception/*,**/SearchCriteria.java,**/EcoNewsSearchRepo.java,**/EventsSearchRepo.java
mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar -Dsonar.projectKey=ita-social-projects-green-city -Dsonar.organization=ita-social-projects -Dsonar.host.url=https://sonarcloud.io -Dsonar.binaries=target/classes -Dsonar.dynamicAnalysis=reuseReports -Dsonar.coverage.exclusions=**/config/*,**/entity/*,**/entity/**,**/exceptions/*,**/exception/*,**/SearchCriteria.java,**/EcoNewsSearchRepo.java,**/EventsSearchRepo.java,**/DatabaseBackupServiceImpl.java
- name: Test Reporter
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/java/greencity/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
"/token",
"/socket/**",
FRIENDS + "/user/{userId}",
"/habit/assign/confirm/{habitAssignId}")
"/habit/assign/confirm/{habitAssignId}",
"/database/backup",
"/database/backupFiles")
.permitAll()
.requestMatchers(HttpMethod.POST,
"/ownSecurity/signUp",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package greencity.controller;

import greencity.constant.HttpStatuses;
import greencity.service.DataBaseBackUpService;
import io.swagger.v3.oas.annotations.Operation;
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 java.time.LocalDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;

@RestController
@RequiredArgsConstructor
@RequestMapping("/database")
public class DataBaseBackUpController {
private final DataBaseBackUpService dataBaseBackUpService;

/**
* Endpoint to initiate the database backup process.
*
* @return ResponseEntity with a success message if the backup is completed
* successfully.
*/
@Operation(summary = "Backup Database")
@ResponseStatus(value = HttpStatus.CREATED)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = HttpStatuses.OK,
content = @Content(examples = @ExampleObject(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 = "403", description = HttpStatuses.FORBIDDEN,
content = @Content(examples = @ExampleObject(HttpStatuses.FORBIDDEN))),
@ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND,
content = @Content(examples = @ExampleObject(HttpStatuses.NOT_FOUND)))
})
@GetMapping("/backup")
public ResponseEntity<String> backupDatabase() {
dataBaseBackUpService.backupDatabase();
return ResponseEntity.ok("Database backup completed successfully!");
}

/**
* Get URLs of backup files from DB for the specified time range.
*
* @param start Start time of the range in ISO date-time format.
* @param end End time of the range in ISO date-time format.
* @return ResponseEntity containing a list of backup URLs.
*/
@Operation(summary = "Get urls of backup files from DB for the specified time range")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = HttpStatuses.OK,
content = @Content(examples = @ExampleObject(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 = "403", description = HttpStatuses.FORBIDDEN,
content = @Content(examples = @ExampleObject(HttpStatuses.FORBIDDEN))),
@ApiResponse(responseCode = "404", description = HttpStatuses.NOT_FOUND,
content = @Content(examples = @ExampleObject(HttpStatuses.NOT_FOUND)))
})
@GetMapping("/backupFiles")
public ResponseEntity<List<String>> getBackupFiles(
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime end) {
return ResponseEntity.ok(dataBaseBackUpService.getBackUpDBUrls(start, end));
}
}
2 changes: 2 additions & 0 deletions core/src/main/resources/application-dev.properties
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ greencityuser.server.address = http://localhost:8060
#AzureCloudStorage
azure.connection.string=${AZURE_CONNECTION_STRING}
azure.container.name=${AZURE_CONTAINER_NAME}

pg.dump.path=${PATH_TO_PG_DUMP:pg_dump}
1 change: 1 addition & 0 deletions core/src/main/resources/application-docker.properties
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,4 @@ greencityuser.server.address = http://user:8060
azure.connection.string=${AZURE_CONNECTION_STRING}
azure.container.name=${AZURE_CONTAINER_NAME}

pg.dump.path=${PATH_TO_PG_DUMP:pg_dump}
1 change: 1 addition & 0 deletions core/src/main/resources/application-prod.properties
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ greencityuser.server.address = ${GREENCITYUSER_SERVER_ADDRESS}
azure.connection.string=${AZURE_CONNECTION_STRING}
azure.container.name=${AZURE_CONTAINER_NAME}

pg.dump.path=${PATH_TO_PG_DUMP:pg_dump}
8 changes: 7 additions & 1 deletion core/src/main/resources/application-test.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@ spring.liquibase.parameters.service-email=test@greencity.ua
tokenKey=1
accessTokenValidTimeInMinutes=120
refreshTokenValidTimeInMinutes=600
verifyEmailTimeHour=24
verifyEmailTimeHour=24

#AzureCloudStorage
azure.connection.string=test
azure.container.name=test

pg.dump.path=${PATH_TO_PG_DUMP:pg_dump}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package greencity.controller;

import greencity.service.DataBaseBackUpService;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
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.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class DataBaseBackUpControllerTest {

private MockMvc mockMvc;

@Mock
private DataBaseBackUpService dataBaseBackUpService;

@InjectMocks
private DataBaseBackUpController controller;

@BeforeEach
void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(controller)
.build();
}

@Test
void testBackupDatabase() throws Exception {
doNothing().when(dataBaseBackUpService).backupDatabase();

mockMvc.perform(MockMvcRequestBuilders.get("/database/backup")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.content().string("Database backup completed successfully!"));
}

@Test
void testGetBackupFiles() throws Exception {
LocalDateTime start = LocalDateTime.now().minusHours(1);
LocalDateTime end = LocalDateTime.now();
List<String> urls = Arrays.asList("http://backup1.com", "http://backup2.com");
when(dataBaseBackUpService.getBackUpDBUrls(any(LocalDateTime.class), any(LocalDateTime.class)))
.thenReturn(urls);

mockMvc.perform(MockMvcRequestBuilders.get("/database/backupFiles")
.param("start", start.toString())
.param("end", end.toString())
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
30 changes: 30 additions & 0 deletions dao/src/main/java/greencity/entity/DataBaseBackUpFiles.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package greencity.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Column;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.Builder;

@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
@Table(name = "db_backups")
public class DataBaseBackUpFiles {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String url;
@Column(name = "created_at")
private LocalDateTime createdAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package greencity.repository;

import greencity.entity.DataBaseBackUpFiles;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface DataBaseBackUpFilesRepo extends JpaRepository<DataBaseBackUpFiles, Long> {
/**
* Finds all backup files created between the specified start and end times.
*
* @param start the start time of the range, inclusive
* @param end the end time of the range, inclusive
* @return a list of backup files created within the specified time range
*/
List<DataBaseBackUpFiles> findAllByCreatedAtBetween(LocalDateTime start, LocalDateTime end);
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,5 @@
<include file="db/changelog/logs/ch-add-unique-constraint-to-user_id-and-achievement_category_id-to-user_action_table.xml"/>
<include file="db/changelog/logs/ch-add-unique-constraint-to-include-habit_id-column-in-user_action-table-Sotnik.xml"/>
<include file="db/changelog/logs/ch-change-content-field-in-chatmessages-table.xml"/>
<include file="db/changelog/logs/ch-add-table-db_backups-kizerov.xml"/>
</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">

<changeSet id="1" author="Kizerov">
<createTable tableName="db_backups">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="url" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="created_at" type="TIMESTAMP">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ public final class ErrorMessage {
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: ";
public static final String INVALID_TIME_RANGE = "Start date and end date must be greater than end date";
public static final String NOT_FOUND_IN_CURRENT_TIME_RANGE = "Not found backups in current time range";

private ErrorMessage() {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package greencity.service;

import java.time.LocalDateTime;
import java.util.List;

/**
* Interface for the service that handles database backup operations.
*/
public interface DataBaseBackUpService {
/**
* Creates a backup of the database. This method should initiate the process of
* backing up the entire database.
*/
void backupDatabase();

/**
* Retrieves URLs of backup files created between the specified start and end
* times.
*
* @param start the start time of the range, inclusive
* @param end the end time of the range, inclusive
* @return a list of URLs of backup files created within the specified time
* range
*/
List<String> getBackUpDBUrls(LocalDateTime start, LocalDateTime end);
}
Loading

0 comments on commit 13394f5

Please sign in to comment.