diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 13cf30e..868293e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,41 +1,22 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Build Project on: - push: - branches: [ "main", "develop" ] pull_request: - branches: [ "main" ] + branches: [ "main", "develop", "release" ] jobs: - build: - - runs-on: ubuntu-latest - + Build: + runs-on: self-hosted steps: - uses: actions/checkout@v4 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: java-version: '21' - distribution: 'corretto' + distribution: 'temurin' cache: maven + - name: Build with Maven env: - DATASOURCE_PASSWORD: ${{ secrets.DATASOURCE_PASSWORD }} - DATASOURCE_URL: ${{ secrets.DATASOURCE_URL }} - DATASOURCE_USERNAME: ${{ secrets.DATASOURCE_USERNAME }} - GOOGLE_ADMIN_CONFIG_TYPE: ${{ secrets.GOOGLE_ADMIN_CONFIG_TYPE }} - GOOGLE_ADMIN_CONFIG_PROJECT_ID: ${{ secrets.GOOGLE_ADMIN_CONFIG_PROJECT_ID }} - GOOGLE_ADMIN_CONFIG_PRIVATE_KEY: ${{ secrets.GOOGLE_ADMIN_CONFIG_PRIVATE_KEY }} - GOOGLE_ADMIN_CONFIG_PRIVATE_KEY_ID: ${{ secrets.GOOGLE_ADMIN_CONFIG_PRIVATE_KEY_ID }} - GOOGLE_ADMIN_CONFIG_CLIENT_EMAIL: ${{ secrets.GOOGLE_ADMIN_CONFIG_CLIENT_EMAIL }} - GOOGLE_ADMIN_CONFIG_CLIENT_ID: ${{ secrets.GOOGLE_ADMIN_CONFIG_CLIENT_ID }} - GOOGLE_ADMIN_CONFIG_CLIENT_X509_CERT_URL: ${{ secrets.GOOGLE_ADMIN_CONFIG_CLIENT_X509_CERT_URL }} + CONFIG_IP: ${{ secrets.DEV_INTEG_HOST }} run: mvn clean install diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 0000000..8a79107 --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,36 @@ +name: Deploy Develop +on: + pull_request: + branches: + - develop + types: + - closed + workflow_dispatch: +jobs: + Deploy: + name: Deploy on Develop + if: ${{ github.event.pull_request.merged == true }} + runs-on: self-hosted + steps: + - name: executing remote ssh commands using ssh key + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.DEV_HOST }} + username: ${{ secrets.HOSTS_USERNAME }} + key: ${{ secrets.DEV_SSH_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd AllConnected/${{ github.event.repository.name }} + echo "Fetching latest code..." + git fetch + git checkout develop + git pull + echo "Building Docker image..." + docker build -t ${{ github.event.repository.name }} . + echo "Creating .env file..." + echo "PROFILE=dev" >> .env + echo "CONFIG_IP=10.43.101.114" >> .env + docker rm -f ${{ github.event.repository.name }} + docker run --name ${{ github.event.repository.name }} --network all_connected -d -p ${{ secrets.SERVICE_PORT }}:8080 --env-file .env ${{ github.event.repository.name }} + echo "Docker container running..." + rm .env diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml new file mode 100644 index 0000000..b292e56 --- /dev/null +++ b/.github/workflows/deploy-prod.yml @@ -0,0 +1,33 @@ +name: Deploy PROD +on: + push: + tags: + - '*' + workflow_dispatch: +jobs: + Deploy: + name: Deploy on PROD + runs-on: self-hosted + steps: + - name: executing remote ssh commands using ssh key + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.PROD_G1_HOST }} + username: ${{ secrets.HOSTS_USERNAME }} + key: ${{ secrets.PROD_G1_SSH_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd AllConnected/${{ github.event.repository.name }} + echo "Fetching latest code..." + git fetch + git checkout main + git pull + echo "Building Docker image..." + docker build -t ${{ github.event.repository.name }} . + echo "Creating .env file..." + echo "PROFILE=prod1" >> .env + echo "CONFIG_IP=10.43.101.72" >> .env + docker rm -f ${{ github.event.repository.name }} + docker run --name ${{ github.event.repository.name }} --network all_connected -d -p ${{ secrets.SERVICE_PORT }}:8080 --env-file .env ${{ github.event.repository.name }} + echo "Docker container running..." + rm .env diff --git a/.github/workflows/deploy-qa.yml b/.github/workflows/deploy-qa.yml new file mode 100644 index 0000000..12b532f --- /dev/null +++ b/.github/workflows/deploy-qa.yml @@ -0,0 +1,36 @@ +name: Deploy QA +on: + pull_request: + branches: + - release + types: + - closed + workflow_dispatch: +jobs: + Deploy: + name: Deploy on QA + if: ${{ github.event.pull_request.merged == true }} + runs-on: self-hosted + steps: + - name: executing remote ssh commands using ssh key + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.QA_G1_HOST }} + username: ${{ secrets.HOSTS_USERNAME }} + key: ${{ secrets.QA_G1_SSH_KEY }} + port: ${{ secrets.SSH_PORT }} + script: | + cd AllConnected/${{ github.event.repository.name }} + echo "Fetching latest code..." + git fetch + git checkout release + git pull + echo "Building Docker image..." + docker build -t ${{ github.event.repository.name }} . + echo "Creating .env file..." + echo "PROFILE=qa1" >> .env + echo "CONFIG_IP=10.43.100.223" >> .env + docker rm -f ${{ github.event.repository.name }} + docker run --name ${{ github.event.repository.name }} --network all_connected -d -p ${{ secrets.SERVICE_PORT }}:8080 --env-file .env ${{ github.event.repository.name }} + echo "Docker container running..." + rm .env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d8ac2ee --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +# Etapa 1: Build +FROM maven:3.9.4-eclipse-temurin-21 AS build + +WORKDIR /app + +# Copiar el pom y código fuente +COPY pom.xml ./ +RUN mvn dependency:go-offline + +COPY src ./src + +RUN mvn clean package -DskipTests + +# Etapa 2: Run +FROM eclipse-temurin:21-jdk-jammy + +WORKDIR /app + +# Copiar el jar generado desde la etapa de build +COPY --from=build /app/target/*.jar app.jar + +EXPOSE 8080 + +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/README.md b/README.md index 55dc48c..dea936c 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,23 @@ Microservicio encargado de gestionar los eventos de la plataforma __AllConnected Para ejecutar este proyecto, necesitarás agregar las siguientes variables de entorno a tu archivo .env -`DATASOURCE_URL` +### Conexion a la base de datos +`DATASOURCE_URL` `DATASOURCE_USERNAME` - `DATASOURCE_PASSWORD` +### Configuración de Firebase +Estas variables son extraidas del archivo de configuración de Firebase en formato JSON + +`GOOGLE_ADMIN_CONFIG_TYPE` +`GOOGLE_ADMIN_CONFIG_PROJECT_ID` +`GOOGLE_ADMIN_CONFIG_PRIVATE_KEY` +`GOOGLE_ADMIN_CONFIG_PRIVATE_KEY_ID` +`GOOGLE_ADMIN_CONFIG_CLIENT_EMAIL` +`GOOGLE_ADMIN_CONFIG_CLIENT_ID` +`GOOGLE_ADMIN_CONFIG_CLIENT_X509_CERT_URL` + --- ## Ejecutar Localmente 💻 diff --git a/pom.xml b/pom.xml index 0004786..b38b702 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,14 @@ org.springframework.cloud spring-cloud-starter-netflix-eureka-client - + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.cloud + spring-cloud-starter-config + org.springframework.boot spring-boot-devtools @@ -74,6 +81,23 @@ reactor-test test + + + com.google.firebase + firebase-admin + 9.1.0 + + + com.google.cloud + google-cloud-storage + 2.11.0 + + + + commons-io + commons-io + 2.16.1 + diff --git a/src/main/java/co/allconnected/fussiontech/eventsservice/config/FirebaseConfig.java b/src/main/java/co/allconnected/fussiontech/eventsservice/config/FirebaseConfig.java new file mode 100644 index 0000000..6d96a9a --- /dev/null +++ b/src/main/java/co/allconnected/fussiontech/eventsservice/config/FirebaseConfig.java @@ -0,0 +1,43 @@ +package co.allconnected.fussiontech.eventsservice.config; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.gson.Gson; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +@Configuration +public class FirebaseConfig { + + private final FirebaseConfigProperties firebaseConfigProperties; + + public FirebaseConfig(FirebaseConfigProperties firebaseConfigProperties) { + this.firebaseConfigProperties = firebaseConfigProperties; + } + + @PostConstruct + public FirebaseApp initializeFirebase() throws IOException { + firebaseConfigProperties.setPrivate_key( + firebaseConfigProperties.getPrivate_key().replace("\\n", "\n") + ); + + String json = new Gson().toJson(firebaseConfigProperties); + + GoogleCredentials credentials = GoogleCredentials.fromStream(new ByteArrayInputStream(json.getBytes())); + + FirebaseOptions options = FirebaseOptions.builder() + .setCredentials(credentials) + .setStorageBucket(firebaseConfigProperties.getProject_id()+".appspot.com") + .build(); + + if(FirebaseApp.getApps().isEmpty()) { + FirebaseApp.initializeApp(options); + } + + return FirebaseApp.getInstance(); + } +} diff --git a/src/main/java/co/allconnected/fussiontech/eventsservice/config/FirebaseConfigProperties.java b/src/main/java/co/allconnected/fussiontech/eventsservice/config/FirebaseConfigProperties.java new file mode 100644 index 0000000..a27d551 --- /dev/null +++ b/src/main/java/co/allconnected/fussiontech/eventsservice/config/FirebaseConfigProperties.java @@ -0,0 +1,22 @@ +package co.allconnected.fussiontech.eventsservice.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "firebase") +@Data +public class FirebaseConfigProperties { + private String type; + private String project_id; + private String private_key; + private String private_key_id; + private String client_email; + private String client_id; + private String auth_uri; + private String token_uri; + private String auth_provider_x509_cert_url; + private String client_x509_cert_url; + private String universe_domain; +} diff --git a/src/main/java/co/allconnected/fussiontech/eventsservice/config/WebConfig.java b/src/main/java/co/allconnected/fussiontech/eventsservice/config/WebConfig.java new file mode 100644 index 0000000..8128a14 --- /dev/null +++ b/src/main/java/co/allconnected/fussiontech/eventsservice/config/WebConfig.java @@ -0,0 +1,18 @@ +package co.allconnected.fussiontech.eventsservice.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true); + } +} diff --git a/src/main/java/co/allconnected/fussiontech/eventsservice/controllers/EventController.java b/src/main/java/co/allconnected/fussiontech/eventsservice/controllers/EventController.java new file mode 100644 index 0000000..191e10a --- /dev/null +++ b/src/main/java/co/allconnected/fussiontech/eventsservice/controllers/EventController.java @@ -0,0 +1,85 @@ +package co.allconnected.fussiontech.eventsservice.controllers; + +import co.allconnected.fussiontech.eventsservice.dtos.EventCreateDto; +import co.allconnected.fussiontech.eventsservice.dtos.EventDto; +import co.allconnected.fussiontech.eventsservice.dtos.Response; +import co.allconnected.fussiontech.eventsservice.services.EventService; +import co.allconnected.fussiontech.eventsservice.utils.OperationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import java.util.List; + +@RestController +@RequestMapping("/api/v0/events") +public class EventController { + // Services + private final EventService eventService; + + @Autowired + public EventController(EventService eventService) { + this.eventService = eventService; + } + + // Get all events + @GetMapping + public ResponseEntity> getAllEvents() { + List events = eventService.getAllEvents(); + return new ResponseEntity<>(events, HttpStatus.OK); + } + + // Get event by ID + @GetMapping("/{id_event}") + public ResponseEntity getEventById(@PathVariable("id_event") Integer id) { + EventDto event = eventService.getEventById(id); + return new ResponseEntity<>(event, HttpStatus.OK); + } + + // Create a new event + @PostMapping + public ResponseEntity createEvent( + @ModelAttribute EventCreateDto eventDto, + @RequestParam(value = "photo", required = false) MultipartFile photo) { + try { + EventDto newEvent = eventService.createEvent(eventDto, photo); + return new ResponseEntity<>(newEvent, HttpStatus.CREATED); + } catch (OperationException e) { + return ResponseEntity.status(e.getCode()).body(new Response(e.getCode(), e.getMessage())); + } catch (RuntimeException e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new Response(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage())); + } + } + + // Update an event + @PutMapping("/{id_event}") + public ResponseEntity updateEvent( + @PathVariable("id_event") Integer id, + @RequestBody EventDto eventDto, + @RequestParam MultipartFile photo) { + EventDto updatedEvent = eventService.updateEvent(id, eventDto, photo); + return new ResponseEntity<>(updatedEvent, HttpStatus.OK); + } + + // Delete an event + @DeleteMapping("/{id_event}") + public ResponseEntity deleteEvent(@PathVariable("id_event") Integer id) { + eventService.deleteEvent(id); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + // Add a label to an event + @PostMapping("/{id_event}/labels") + public ResponseEntity addLabelToEvent(@PathVariable("id_event") Integer eventId, @RequestBody Integer labelId) { + EventDto updatedEvent = eventService.addLabelToEvent(eventId, labelId); + return new ResponseEntity<>(updatedEvent, HttpStatus.CREATED); + } + + // Remove a label from an event + @DeleteMapping("/{id_event}/labels/{id_label}") + public ResponseEntity removeLabelFromEvent(@PathVariable("id_event") Integer eventId, @PathVariable("id_label") Integer labelId) { + eventService.removeLabelFromEvent(eventId, labelId); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/src/main/java/co/allconnected/fussiontech/eventsservice/controllers/FooController.java b/src/main/java/co/allconnected/fussiontech/eventsservice/controllers/FooController.java deleted file mode 100644 index d79e2c0..0000000 --- a/src/main/java/co/allconnected/fussiontech/eventsservice/controllers/FooController.java +++ /dev/null @@ -1,12 +0,0 @@ -package co.allconnected.fussiontech.eventsservice.controllers; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class FooController { - @GetMapping("/foo") - public String fooBar() { - return "Foo Bar"; - } -} diff --git a/src/main/java/co/allconnected/fussiontech/eventsservice/controllers/GreetController.java b/src/main/java/co/allconnected/fussiontech/eventsservice/controllers/GreetController.java deleted file mode 100644 index e807b6d..0000000 --- a/src/main/java/co/allconnected/fussiontech/eventsservice/controllers/GreetController.java +++ /dev/null @@ -1,12 +0,0 @@ -package co.allconnected.fussiontech.eventsservice.controllers; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class GreetController { - @GetMapping("/") - public String index() { - return "Hello world!"; - } -} diff --git a/src/main/java/co/allconnected/fussiontech/eventsservice/controllers/LabelController.java b/src/main/java/co/allconnected/fussiontech/eventsservice/controllers/LabelController.java new file mode 100644 index 0000000..e2be42a --- /dev/null +++ b/src/main/java/co/allconnected/fussiontech/eventsservice/controllers/LabelController.java @@ -0,0 +1,57 @@ +package co.allconnected.fussiontech.eventsservice.controllers; + +import co.allconnected.fussiontech.eventsservice.dtos.LabelDto; +import co.allconnected.fussiontech.eventsservice.services.LabelService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v0/labels") +public class LabelController { + + private final LabelService labelService; + + @Autowired + public LabelController(LabelService labelService) { + this.labelService = labelService; + } + + // Create a new label + @PostMapping + public ResponseEntity createLabel(@RequestBody LabelDto labelDto) { + LabelDto createdLabel = labelService.createLabel(labelDto); + return new ResponseEntity<>(createdLabel, HttpStatus.CREATED); + } + + // Get all labels + @GetMapping + public ResponseEntity> getAllLabels() { + List labels = labelService.getAllLabels(); + return new ResponseEntity<>(labels, HttpStatus.OK); + } + + // Get a label by ID + @GetMapping("/{id_label}") + public ResponseEntity getLabelById(@PathVariable Integer id_label) { + LabelDto label = labelService.getLabelById(id_label); + return new ResponseEntity<>(label, HttpStatus.OK); + } + + // Update the label by ID + @PutMapping("/{id_label}") + public ResponseEntity updateLabel(@PathVariable Integer id_label, @RequestBody LabelDto labelDto) { + LabelDto updatedLabel = labelService.put(id_label, labelDto); + return new ResponseEntity<>(updatedLabel, HttpStatus.OK); + } + + // Delete a label by ID + @DeleteMapping("/{id_label}") + public ResponseEntity deleteLabel(@PathVariable Integer id_label) { + labelService.deleteLabel(id_label); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/src/main/java/co/allconnected/fussiontech/eventsservice/dtos/EventCreateDto.java b/src/main/java/co/allconnected/fussiontech/eventsservice/dtos/EventCreateDto.java new file mode 100644 index 0000000..6213411 --- /dev/null +++ b/src/main/java/co/allconnected/fussiontech/eventsservice/dtos/EventCreateDto.java @@ -0,0 +1,26 @@ +package co.allconnected.fussiontech.eventsservice.dtos; + + +import co.allconnected.fussiontech.eventsservice.models.Label; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; +import java.time.Instant; +import java.util.Set; + +/** + * DTO for {@link co.allconnected.fussiontech.eventsservice.models.Event} + */ +@Data +@Builder +@AllArgsConstructor +public class EventCreateDto implements Serializable { + private String name; + private String description; + private String photoUrl; + private Integer capacity; + private Instant date; + private Double price; +} \ No newline at end of file diff --git a/src/main/java/co/allconnected/fussiontech/eventsservice/dtos/EventDto.java b/src/main/java/co/allconnected/fussiontech/eventsservice/dtos/EventDto.java new file mode 100644 index 0000000..1d13124 --- /dev/null +++ b/src/main/java/co/allconnected/fussiontech/eventsservice/dtos/EventDto.java @@ -0,0 +1,30 @@ +package co.allconnected.fussiontech.eventsservice.dtos; + +import co.allconnected.fussiontech.eventsservice.models.EventParticipant; +import co.allconnected.fussiontech.eventsservice.models.Label; +import lombok.*; + +import java.io.Serializable; +import java.time.Instant; +import java.util.Set; +import java.util.UUID; + +/** + * DTO for {@link co.allconnected.fussiontech.eventsservice.models.Event} + */ +@Data +@Builder +@AllArgsConstructor +public class EventDto implements Serializable { + private Integer id; + private String idBusiness; + private String name; + private String description; + private String photoUrl; + private Integer capacity; + private Instant date; + private Boolean active; + private Double price; + private Set