diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 7aa52d55..f3122311 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -1,3 +1,5 @@ +name: sonarCloud + on: push: branches: @@ -8,17 +10,20 @@ on: jobs: test: runs-on: ubuntu-latest - strategy: - matrix: - java-version: [ 17 ] + services: + redis: + image: redis:latest + ports: + - 6379:6379 + steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Java uses: actions/setup-java@v3 with: - java-version: ${{ matrix.java-version }} + java-version: 17 distribution: 'adopt' - name: Grant execute permission for gradlew @@ -31,14 +36,8 @@ jobs: key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - - name: Setup Gradle - uses: gradle/gradle-build-action@v2 - with: - arguments: check - cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/develop' }} - - - name: SonarCloud scan + - name: Run tests and SonarCloud scan env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew sonar --info --stacktrace \ No newline at end of file + run: ./gradlew test sonar --info \ No newline at end of file diff --git a/.gitignore b/.gitignore index 53cee62d..9b69782d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ out/ .vscode/ /config.yml /src/main/resources/logback.xml +docker-compose.yml diff --git a/build.gradle b/build.gradle index f8360e7e..d5af54a3 100644 --- a/build.gradle +++ b/build.gradle @@ -67,6 +67,7 @@ dependencies { testImplementation "org.testcontainers:mysql:1.19.8" testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' + } def jacocoDir = layout.buildDirectory.dir("reports/") @@ -112,7 +113,8 @@ jacocoTestReport { '**/vo/**', '**/dto/**', '**/entity/**', - '**/response/**' + '**/response/**', + '**/component/LambdaKey' ]) }) ) diff --git a/src/main/java/site/iris/issuefy/component/LambdaKey.java b/src/main/java/site/iris/issuefy/component/LambdaKey.java new file mode 100644 index 00000000..536415d0 --- /dev/null +++ b/src/main/java/site/iris/issuefy/component/LambdaKey.java @@ -0,0 +1,19 @@ +package site.iris.issuefy.component; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Component +@Getter +@NoArgsConstructor +public class LambdaKey { + @Value("${jwt.lambdaKey}") + private String key; + + public LambdaKey(String key) { + this.key = key; + } +} diff --git a/src/main/java/site/iris/issuefy/config/CorsConfig.java b/src/main/java/site/iris/issuefy/config/CorsConfig.java index 163ede5a..7497ccf3 100644 --- a/src/main/java/site/iris/issuefy/config/CorsConfig.java +++ b/src/main/java/site/iris/issuefy/config/CorsConfig.java @@ -9,10 +9,9 @@ public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/api/**") // CORS를 적용할 URL 패턴 설정 - .allowedOrigins("*") - .allowedOrigins("http://localhost:3000") // 허용할 Origin 설정 - .allowedMethods("GET", "POST", "PUT", "DELETE") // 허용할 HTTP 메서드 설정 - .allowedHeaders("*"); // 허용할 HTTP 헤더 설정 + registry.addMapping("/api/**") + .allowedOrigins("http://localhost:3000", "https://issuefy.site") + .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS") + .allowedHeaders("*"); } } diff --git a/src/main/java/site/iris/issuefy/config/FilterConfig.java b/src/main/java/site/iris/issuefy/config/FilterConfig.java index 36c3d460..4da3ac12 100644 --- a/src/main/java/site/iris/issuefy/config/FilterConfig.java +++ b/src/main/java/site/iris/issuefy/config/FilterConfig.java @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Configuration; import lombok.RequiredArgsConstructor; +import site.iris.issuefy.component.LambdaKey; import site.iris.issuefy.filter.JwtAuthenticationFilter; import site.iris.issuefy.service.TokenProvider; @@ -13,13 +14,13 @@ public class FilterConfig { private final TokenProvider tokenProvider; + private final LambdaKey lambdaKey; @Bean public FilterRegistrationBean jwtAuthenticationFilter() { FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); - registrationBean.setFilter(new JwtAuthenticationFilter(tokenProvider)); - registrationBean.addUrlPatterns("/api/*"); - + registrationBean.setFilter(new JwtAuthenticationFilter(tokenProvider, lambdaKey)); + registrationBean.addUrlPatterns("/*"); return registrationBean; } } diff --git a/src/main/java/site/iris/issuefy/config/RedisConfig.java b/src/main/java/site/iris/issuefy/config/RedisConfig.java index f3733ef7..a7e7adc8 100644 --- a/src/main/java/site/iris/issuefy/config/RedisConfig.java +++ b/src/main/java/site/iris/issuefy/config/RedisConfig.java @@ -1,20 +1,42 @@ package site.iris.issuefy.config; -import java.io.Serializable; - import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; -import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.data.redis.listener.PatternTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; + +import site.iris.issuefy.service.NotificationService; @Configuration public class RedisConfig { @Bean - public RedisTemplate redisTemplate(LettuceConnectionFactory connectionFactory) { - RedisTemplate template = new RedisTemplate<>(); - template.setConnectionFactory(connectionFactory); - return template; + public RedisMessageListenerContainer container(LettuceConnectionFactory connectionFactory, + MessageListenerAdapter listenerAdapter) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + container.addMessageListener(listenerAdapter, sseMessagesTopic()); + container.addMessageListener(listenerAdapter, repositoryUpdatesTopic()); + return container; + } + + @Bean + public MessageListenerAdapter listenerAdapter(NotificationService notificationService) { + return new MessageListenerAdapter(notificationService, "handleRedisMessage"); + } + + @Bean + public ChannelTopic sseMessagesTopic() { + return new ChannelTopic("sse:messages"); + } + + @Bean + public PatternTopic repositoryUpdatesTopic() { + return new PatternTopic("repository_updates"); } + } diff --git a/src/main/java/site/iris/issuefy/config/SseConfig.java b/src/main/java/site/iris/issuefy/config/SseConfig.java new file mode 100644 index 00000000..3131c6db --- /dev/null +++ b/src/main/java/site/iris/issuefy/config/SseConfig.java @@ -0,0 +1,15 @@ +package site.iris.issuefy.config; + +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +@Configuration +public class SseConfig { + @Bean + public ConcurrentHashMap SseConnectionMap() { + return new ConcurrentHashMap<>(); + } +} diff --git a/src/main/java/site/iris/issuefy/controller/NotificationController.java b/src/main/java/site/iris/issuefy/controller/NotificationController.java new file mode 100644 index 00000000..4b2aa5d8 --- /dev/null +++ b/src/main/java/site/iris/issuefy/controller/NotificationController.java @@ -0,0 +1,40 @@ +package site.iris.issuefy.controller; + +import java.util.List; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import site.iris.issuefy.model.dto.NotificationDto; +import site.iris.issuefy.model.dto.NotificationReadDto; +import site.iris.issuefy.service.NotificationService; + +@RestController +@RequiredArgsConstructor +@RequestMapping("api/notifications") +@Slf4j +public class NotificationController { + + private final NotificationService notificationService; + + @GetMapping + public ResponseEntity> getNotifications(@RequestAttribute String githubId) { + List notificationDtos = notificationService.findNotifications(githubId); + log.info(notificationDtos.toString()); + return ResponseEntity.ok(notificationDtos); + } + + @PatchMapping + public ResponseEntity updateNotification(@RequestBody NotificationReadDto notificationReadDto) { + notificationService.updateUserNotificationsAsRead(notificationReadDto); + return ResponseEntity.ok().build(); + } + +} diff --git a/src/main/java/site/iris/issuefy/controller/SseController.java b/src/main/java/site/iris/issuefy/controller/SseController.java new file mode 100644 index 00000000..dcf5169c --- /dev/null +++ b/src/main/java/site/iris/issuefy/controller/SseController.java @@ -0,0 +1,52 @@ +package site.iris.issuefy.controller; + +import java.io.IOException; + +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import site.iris.issuefy.model.dto.UpdateRepositoryDto; +import site.iris.issuefy.service.NotificationService; + +@RestController +@RequiredArgsConstructor +@Slf4j +public class SseController { + private final NotificationService notificationService; + + @GetMapping(value = "/api/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public ResponseEntity connect(@RequestAttribute String githubId) { + log.info("New connection for user: {}", githubId); + SseEmitter emitter = new SseEmitter(60000L); + notificationService.addEmitter(githubId, emitter); + notificationService.sendInitialNotification(githubId, emitter); + + emitter.onTimeout(() -> { + log.info("SSE connection timed out for user: {}", githubId); + notificationService.removeEmitter(githubId); + try { + emitter.send(SseEmitter.event().name("error").data("Connection timed out")); + } catch (IOException e) { + log.error("Error sending timeout message", e); + } finally { + emitter.complete(); + } + }); + + return ResponseEntity.ok(emitter); + } + + @PostMapping("/api/receive") + public void receive(@RequestBody UpdateRepositoryDto updateRepositoryDto) { + log.info("lambda request receive"); + notificationService.handleRedisMessage(updateRepositoryDto); + } +} diff --git a/src/main/java/site/iris/issuefy/entity/Notification.java b/src/main/java/site/iris/issuefy/entity/Notification.java new file mode 100644 index 00000000..57400419 --- /dev/null +++ b/src/main/java/site/iris/issuefy/entity/Notification.java @@ -0,0 +1,53 @@ +package site.iris.issuefy.entity; + +import java.time.LocalDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Table(name = "notification") +public class Notification { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "repository_id", nullable = false) + private Repository repository; + + @Column(name = "repository_name", nullable = false) + private String repositoryName; + + @Column(name = "push_time", nullable = false) + private LocalDateTime pushTime; + + public Notification(Repository repository, String repositoryName, LocalDateTime pushTime) { + this.repository = repository; + this.repositoryName = repositoryName; + this.pushTime = pushTime; + } + + @Override + public String toString() { + return "Notification{" + + "id=" + id + + ", repository=" + repository + + ", message='" + repositoryName + '\'' + + ", pushTime=" + pushTime + + '}'; + } +} diff --git a/src/main/java/site/iris/issuefy/entity/Org.java b/src/main/java/site/iris/issuefy/entity/Org.java index c238b29a..e12565a0 100644 --- a/src/main/java/site/iris/issuefy/entity/Org.java +++ b/src/main/java/site/iris/issuefy/entity/Org.java @@ -6,6 +6,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -13,6 +14,7 @@ @Table(name = "organization") @Getter @NoArgsConstructor +@AllArgsConstructor public class Org { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/site/iris/issuefy/entity/Repository.java b/src/main/java/site/iris/issuefy/entity/Repository.java index d65201ed..1d797fbf 100644 --- a/src/main/java/site/iris/issuefy/entity/Repository.java +++ b/src/main/java/site/iris/issuefy/entity/Repository.java @@ -9,12 +9,14 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Table(name = "repository") @Getter +@AllArgsConstructor @NoArgsConstructor public class Repository { @Id diff --git a/src/main/java/site/iris/issuefy/entity/Subscription.java b/src/main/java/site/iris/issuefy/entity/Subscription.java index 2179b290..40f8b721 100644 --- a/src/main/java/site/iris/issuefy/entity/Subscription.java +++ b/src/main/java/site/iris/issuefy/entity/Subscription.java @@ -8,11 +8,13 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @NoArgsConstructor +@AllArgsConstructor @Getter @Table(name = "subscription") public class Subscription { diff --git a/src/main/java/site/iris/issuefy/entity/User.java b/src/main/java/site/iris/issuefy/entity/User.java index f5ea2c7d..1b5efc7e 100644 --- a/src/main/java/site/iris/issuefy/entity/User.java +++ b/src/main/java/site/iris/issuefy/entity/User.java @@ -7,10 +7,12 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @Entity +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Table(name = "user") diff --git a/src/main/java/site/iris/issuefy/entity/UserNotification.java b/src/main/java/site/iris/issuefy/entity/UserNotification.java new file mode 100644 index 00000000..5549183f --- /dev/null +++ b/src/main/java/site/iris/issuefy/entity/UserNotification.java @@ -0,0 +1,41 @@ +package site.iris.issuefy.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "user_notification") +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class UserNotification { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @ManyToOne + @JoinColumn(name = "notification_id", nullable = false) + private Notification notification; + + @Column(name = "is_read", nullable = false) + private Boolean isRead; + + public UserNotification(User user, Notification notification) { + this.user = user; + this.notification = notification; + this.isRead = false; + } +} diff --git a/src/main/java/site/iris/issuefy/filter/JwtAuthenticationFilter.java b/src/main/java/site/iris/issuefy/filter/JwtAuthenticationFilter.java index 4e0ae64d..e4fe06e0 100644 --- a/src/main/java/site/iris/issuefy/filter/JwtAuthenticationFilter.java +++ b/src/main/java/site/iris/issuefy/filter/JwtAuthenticationFilter.java @@ -15,6 +15,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import site.iris.issuefy.component.LambdaKey; import site.iris.issuefy.exception.UnauthenticatedException; import site.iris.issuefy.service.TokenProvider; @@ -23,6 +24,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { public static final String BEARER_DELIMITER = "Bearer "; private final TokenProvider tokenProvider; + private final LambdaKey lambdaKey; @Override protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @@ -36,7 +38,7 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull Ht // 프리플라이트 요청 처리 if (request.getMethod().equals("OPTIONS")) { - handlePreflightRequest(response); + filterChain.doFilter(request, response); return; } @@ -46,6 +48,14 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull Ht return; } + if (path.startsWith("/api/receive")) { + String bearerToken = request.getHeader(AUTHORIZATION); + if (bearerToken.equals(BEARER_DELIMITER + lambdaKey.getKey())) { + filterChain.doFilter(request, response); + return; + } + } + String githubId = null; try { String token = getJwtFromRequest(request); @@ -67,13 +77,6 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull Ht } } - private void handlePreflightRequest(HttpServletResponse response) { - response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"); - response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); - response.setHeader("Access-Control-Allow-Headers", "*"); - response.setStatus(HttpServletResponse.SC_OK); - } - private void handleUnauthorizedException(HttpServletResponse response, UnauthenticatedException e) throws IOException { response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"); diff --git a/src/main/java/site/iris/issuefy/model/dto/GithubOrgDto.java b/src/main/java/site/iris/issuefy/model/dto/GithubOrgDto.java index 8a893e50..cc968584 100644 --- a/src/main/java/site/iris/issuefy/model/dto/GithubOrgDto.java +++ b/src/main/java/site/iris/issuefy/model/dto/GithubOrgDto.java @@ -1,10 +1,12 @@ package site.iris.issuefy.model.dto; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @NoArgsConstructor +@AllArgsConstructor public class GithubOrgDto { private long id; private String login; diff --git a/src/main/java/site/iris/issuefy/model/dto/GithubRepositoryDto.java b/src/main/java/site/iris/issuefy/model/dto/GithubRepositoryDto.java index c9329d23..10db4934 100644 --- a/src/main/java/site/iris/issuefy/model/dto/GithubRepositoryDto.java +++ b/src/main/java/site/iris/issuefy/model/dto/GithubRepositoryDto.java @@ -2,10 +2,12 @@ import java.time.LocalDateTime; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @Getter +@AllArgsConstructor @NoArgsConstructor public class GithubRepositoryDto { private long id; diff --git a/src/main/java/site/iris/issuefy/model/dto/NotificationDto.java b/src/main/java/site/iris/issuefy/model/dto/NotificationDto.java new file mode 100644 index 00000000..ee65fa0e --- /dev/null +++ b/src/main/java/site/iris/issuefy/model/dto/NotificationDto.java @@ -0,0 +1,33 @@ +package site.iris.issuefy.model.dto; + +import java.time.LocalDateTime; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class NotificationDto { + private Long userNotificationId; + private String orgName; + private String repositoryName; + private LocalDateTime notificationCreatedAt; + private boolean isRead; + + public static NotificationDto of(Long userNotificationId, String orgName, String repositoryName, + LocalDateTime localDateTime, + boolean isRead) { + return new NotificationDto(userNotificationId, orgName, repositoryName, localDateTime, isRead); + } + + @Override + public String toString() { + return "NotificationDto{" + + "userNotificationId=" + userNotificationId + + ", orgName='" + orgName + '\'' + + ", repositoryName='" + repositoryName + '\'' + + ", notificationCreatedAt=" + notificationCreatedAt + + ", isRead=" + isRead + + '}'; + } +} diff --git a/src/main/java/site/iris/issuefy/model/dto/NotificationReadDto.java b/src/main/java/site/iris/issuefy/model/dto/NotificationReadDto.java new file mode 100644 index 00000000..a7445059 --- /dev/null +++ b/src/main/java/site/iris/issuefy/model/dto/NotificationReadDto.java @@ -0,0 +1,16 @@ +package site.iris.issuefy.model.dto; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Data +@NoArgsConstructor +@AllArgsConstructor +public class NotificationReadDto { + List userNotificationIds; +} diff --git a/src/main/java/site/iris/issuefy/model/dto/TestDto.java b/src/main/java/site/iris/issuefy/model/dto/TestDto.java new file mode 100644 index 00000000..ab99a8aa --- /dev/null +++ b/src/main/java/site/iris/issuefy/model/dto/TestDto.java @@ -0,0 +1,12 @@ +package site.iris.issuefy.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class TestDto { + private long id; + private String message; + +} diff --git a/src/main/java/site/iris/issuefy/model/dto/UnreadNotificationDto.java b/src/main/java/site/iris/issuefy/model/dto/UnreadNotificationDto.java new file mode 100644 index 00000000..dd92b303 --- /dev/null +++ b/src/main/java/site/iris/issuefy/model/dto/UnreadNotificationDto.java @@ -0,0 +1,12 @@ +package site.iris.issuefy.model.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; + +@Data +@AllArgsConstructor +@Getter +public class UnreadNotificationDto { + private int unreadCount; +} diff --git a/src/main/java/site/iris/issuefy/model/dto/UpdateRepositoryDto.java b/src/main/java/site/iris/issuefy/model/dto/UpdateRepositoryDto.java new file mode 100644 index 00000000..dcab7b32 --- /dev/null +++ b/src/main/java/site/iris/issuefy/model/dto/UpdateRepositoryDto.java @@ -0,0 +1,21 @@ +package site.iris.issuefy.model.dto; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class UpdateRepositoryDto { + List updatedRepositoryIds; + + @Override + public String toString() { + return "UpdateRepositoryDto{" + + "updatedRepositoryIds=" + updatedRepositoryIds + + '}'; + } +} diff --git a/src/main/java/site/iris/issuefy/repository/LabelRepository.java b/src/main/java/site/iris/issuefy/repository/LabelRepository.java index 23be7527..adb7fbf0 100644 --- a/src/main/java/site/iris/issuefy/repository/LabelRepository.java +++ b/src/main/java/site/iris/issuefy/repository/LabelRepository.java @@ -9,5 +9,6 @@ public interface LabelRepository extends CrudRepository { Optional