Большой рефакторинг
This commit is contained in:
parent
d1f3bcebb0
commit
8bc397dc8c
@ -7,6 +7,6 @@ package com.tsc.bitbucketbot.domain;
|
||||
*/
|
||||
public enum PullRequestStatus {
|
||||
|
||||
OPEN, MERGED, DECLINED
|
||||
OPEN, MERGED, DECLINED, DELETE
|
||||
|
||||
}
|
||||
|
16
src/main/java/com/tsc/bitbucketbot/dto/IdAndStatusPr.java
Normal file
16
src/main/java/com/tsc/bitbucketbot/dto/IdAndStatusPr.java
Normal file
@ -0,0 +1,16 @@
|
||||
package com.tsc.bitbucketbot.dto;
|
||||
|
||||
import com.tsc.bitbucketbot.domain.PullRequestStatus;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class IdAndStatusPr {
|
||||
|
||||
private Long id;
|
||||
private PullRequestStatus status;
|
||||
|
||||
}
|
@ -1,19 +1,20 @@
|
||||
package com.tsc.bitbucketbot.repository.jpa;
|
||||
|
||||
import com.tsc.bitbucketbot.domain.PullRequestStatus;
|
||||
import com.tsc.bitbucketbot.domain.ReviewerStatus;
|
||||
import com.tsc.bitbucketbot.domain.entity.PullRequest;
|
||||
import com.tsc.bitbucketbot.dto.IdAndStatusPr;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* TODO: Добавить описание интерфейса.
|
||||
*
|
||||
* @author upagge [31.01.2020]
|
||||
*/
|
||||
public interface PullRequestsRepository extends JpaRepository<PullRequest, Long> {
|
||||
@ -33,7 +34,13 @@ public interface PullRequestsRepository extends JpaRepository<PullRequest, Long>
|
||||
@Query("SELECT p FROM PullRequest p LEFT JOIN p.reviewers r WHERE p.author.login=:author AND r.status=:reviewerStatus")
|
||||
List<PullRequest> findAllByAuthorAndReviewerStatus(@Param("author") String author, @Param("reviewerStatus") ReviewerStatus reviewerStatus);
|
||||
|
||||
@Query("SELECT new com.tsc.bitbucketbot.dto.IdAndStatusPr(p.id, p.status) FROM PullRequest p WHERE p.status IN :statuses")
|
||||
Set<IdAndStatusPr> findAllIdByStatusIn(@Param("statuses") Collection<PullRequestStatus> statuses);
|
||||
|
||||
@Query("SELECT p.id from PullRequest p")
|
||||
Set<Long> getAllIds();
|
||||
Set<Long> findAllIds();
|
||||
|
||||
@Query("SELECT p FROM PullRequest p WHERE p.author.login = :login AND p.createDate BETWEEN :dateFrom AND :dateTo")
|
||||
List<PullRequest> findAllByAuthorAndDateBetween(@Param("login") String login, @Param("dateFrom") LocalDateTime dateFrom, @Param("dateTo") LocalDateTime dateTo);
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import com.tsc.bitbucketbot.domain.entity.PullRequest;
|
||||
import com.tsc.bitbucketbot.domain.entity.User;
|
||||
import com.tsc.bitbucketbot.service.MessageSendService;
|
||||
import com.tsc.bitbucketbot.service.PullRequestsService;
|
||||
import com.tsc.bitbucketbot.service.ReportService;
|
||||
import com.tsc.bitbucketbot.service.UserService;
|
||||
import com.tsc.bitbucketbot.utils.Message;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -21,6 +22,7 @@ public class SchedulerNotification {
|
||||
private final UserService userService;
|
||||
private final PullRequestsService pullRequestsService;
|
||||
private final MessageSendService messageSendService;
|
||||
private final ReportService reportService;
|
||||
|
||||
// Утреннее сообщение
|
||||
@Scheduled(cron = "0 15 8 * * MON-FRI")
|
||||
@ -41,17 +43,26 @@ public class SchedulerNotification {
|
||||
}
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0 18 * * FRI")
|
||||
// @Scheduled(cron = "0 0 18 * * FRI")
|
||||
// @Scheduled(fixedRate = 30000)
|
||||
public void goodWeekEnd() {
|
||||
List<User> allRegister = userService.getAllRegister();
|
||||
for (User user : allRegister) {
|
||||
// List<User> allRegister = userService.getAllRegister();
|
||||
// for (User user : allRegister) {
|
||||
// messageSendService.add(
|
||||
// MessageSend.builder()
|
||||
// .telegramId(user.getTelegramId())
|
||||
// .message(Message.goodWeekEnd())
|
||||
// .build()
|
||||
// );
|
||||
// reportService.generateReport(user.getLogin());
|
||||
// }
|
||||
final User user = userService.getByLogin("mstruchkov").get();
|
||||
messageSendService.add(
|
||||
MessageSend.builder()
|
||||
.telegramId(user.getTelegramId())
|
||||
.message(Message.goodWeekEnd())
|
||||
.message(reportService.generateReport(user.getLogin()))
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,13 +8,11 @@ import com.tsc.bitbucketbot.domain.entity.PullRequest;
|
||||
import com.tsc.bitbucketbot.domain.entity.Reviewer;
|
||||
import com.tsc.bitbucketbot.domain.entity.User;
|
||||
import com.tsc.bitbucketbot.domain.util.ReviewerChange;
|
||||
import com.tsc.bitbucketbot.dto.bitbucket.PullRequestJson;
|
||||
import com.tsc.bitbucketbot.dto.bitbucket.sheet.PullRequestSheetJson;
|
||||
import com.tsc.bitbucketbot.service.MessageSendService;
|
||||
import com.tsc.bitbucketbot.service.PullRequestsService;
|
||||
import com.tsc.bitbucketbot.service.UserService;
|
||||
import com.tsc.bitbucketbot.service.Utils;
|
||||
import com.tsc.bitbucketbot.service.converter.PullRequestJsonConverter;
|
||||
import com.tsc.bitbucketbot.utils.Message;
|
||||
import com.tsc.bitbucketbot.utils.Pair;
|
||||
import com.tsc.bitbucketbot.utils.Smile;
|
||||
@ -35,8 +33,6 @@ import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* TODO: Добавить описание класса.
|
||||
*
|
||||
* @author upagge [30.01.2020]
|
||||
*/
|
||||
@Service
|
||||
@ -51,53 +47,60 @@ public class SchedulerPullRequest {
|
||||
|
||||
@Scheduled(fixedRate = 30000)
|
||||
public void checkOldPullRequest() {
|
||||
Set<Long> existsId = pullRequestsService.getAllId();
|
||||
Set<Long> openId = checkOpenPullRequest();
|
||||
checkClosePullRequest();
|
||||
existsId.removeAll(openId);
|
||||
if (!existsId.isEmpty()) {
|
||||
pullRequestsService.deleteAll(existsId);
|
||||
final Set<Long> existsId = pullRequestsService.getAllId();
|
||||
final Set<Long> openId = checkOpenPullRequest();
|
||||
final Set<Long> closeId = checkClosePullRequest();
|
||||
final Set<Long> newNotExistsId = existsId.stream()
|
||||
.filter(id -> !openId.contains(id) && !closeId.contains(id))
|
||||
.collect(Collectors.toSet());
|
||||
if (!newNotExistsId.isEmpty()) {
|
||||
updateDeletePr(newNotExistsId);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkClosePullRequest() {
|
||||
private void updateDeletePr(@NonNull Set<Long> ids) {
|
||||
final Set<PullRequest> deletePr = pullRequestsService.getAllById(ids);
|
||||
deletePr.stream()
|
||||
.filter(pullRequest -> pullRequest.getAuthor().getTelegramId() != null)
|
||||
.forEach(pullRequest -> messageSendService.add(
|
||||
MessageSend.builder()
|
||||
.telegramId(pullRequest.getAuthor().getTelegramId())
|
||||
.message(Message.statusPullRequest(pullRequest.getName(), pullRequest.getUrl(), pullRequest.getStatus(), PullRequestStatus.DELETE))
|
||||
.build()
|
||||
));
|
||||
pullRequestsService.updateAll(
|
||||
deletePr.stream()
|
||||
.peek(pullRequest -> pullRequest.setStatus(PullRequestStatus.DELETE))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
private Set<Long> checkClosePullRequest() {
|
||||
final List<User> users = userService.getAllRegister();
|
||||
final Set<Long> ids = new HashSet<>();
|
||||
for (User user : users) {
|
||||
Optional<PullRequestSheetJson> sheetJson = Utils.urlToJson(bitbucketConfig.getUrlPullRequestClose(), user.getToken(), PullRequestSheetJson.class);
|
||||
while (sheetJson.isPresent() && sheetJson.get().getValues() != null && !sheetJson.get().getValues().isEmpty()) {
|
||||
final PullRequestSheetJson bitbucketSheet = sheetJson.get();
|
||||
final List<PullRequestJson> jsonsPr = bitbucketSheet.getValues().stream()
|
||||
.filter(
|
||||
jsonPr -> pullRequestsService.existsByBitbucketIdAndReposId(
|
||||
jsonPr.getId(),
|
||||
jsonPr.getFromRef().getRepository().getId()
|
||||
)
|
||||
)
|
||||
final List<PullRequest> newPrs = bitbucketSheet.getValues().stream()
|
||||
.map(jsonPr -> conversionService.convert(jsonPr, PullRequest.class))
|
||||
.peek(pullRequest -> pullRequestsService.getIdByBitbucketIdAndReposId(pullRequest.getBitbucketId(), pullRequest.getRepositoryId()).ifPresent(pullRequest::setId))
|
||||
.filter(pullRequest -> pullRequest.getId() != null)
|
||||
.collect(Collectors.toList());
|
||||
final Set<Long> idPr = jsonsPr.stream()
|
||||
.map(
|
||||
jsonPr -> pullRequestsService.getIdByBitbucketIdAndReposId(
|
||||
jsonPr.getId(),
|
||||
jsonPr.getFromRef().getRepository().getId()
|
||||
)
|
||||
)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.collect(Collectors.toSet());
|
||||
for (PullRequestJson jsonPr : jsonsPr) {
|
||||
final Optional<User> optUser = userService.getByLogin(jsonPr.getAuthor().getUser().getName());
|
||||
if (optUser.isPresent()) {
|
||||
final User author = optUser.get();
|
||||
for (PullRequest pullRequest : newPrs) {
|
||||
final User author = pullRequest.getAuthor();
|
||||
final Long telegramId = author.getTelegramId();
|
||||
if (telegramId != null) {
|
||||
final PullRequestStatus statusPr = PullRequestJsonConverter.convertPullRequestStatus(jsonPr.getState());
|
||||
final String message = Message.statusPullRequest(jsonPr.getTitle(), jsonPr.getLinks().getSelf().get(0).getHref(), PullRequestStatus.OPEN, statusPr);
|
||||
final String message = Message.statusPullRequest(pullRequest.getName(), pullRequest.getUrl(), PullRequestStatus.OPEN, pullRequest.getStatus());
|
||||
messageSendService.add(MessageSend.builder().telegramId(telegramId).message(message).build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pullRequestsService.deleteAll(idPr);
|
||||
ids.addAll(
|
||||
pullRequestsService.updateAll(newPrs).stream()
|
||||
.map(PullRequest::getId)
|
||||
.collect(Collectors.toSet())
|
||||
);
|
||||
|
||||
if (bitbucketSheet.getNextPageStart() != null) {
|
||||
sheetJson = Utils.urlToJson(bitbucketConfig.getUrlPullRequestClose() + bitbucketSheet.getNextPageStart(), bitbucketConfig.getToken(), PullRequestSheetJson.class);
|
||||
@ -106,6 +109,7 @@ public class SchedulerPullRequest {
|
||||
}
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
private Set<Long> checkOpenPullRequest() {
|
||||
|
@ -1,15 +0,0 @@
|
||||
package com.tsc.bitbucketbot.scheduler;
|
||||
|
||||
import com.tsc.bitbucketbot.service.ReportService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class SchedulerReport {
|
||||
|
||||
public final ReportService reportService;
|
||||
|
||||
}
|
@ -6,6 +6,7 @@ import com.tsc.bitbucketbot.domain.entity.PullRequest;
|
||||
import lombok.NonNull;
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@ -36,4 +37,6 @@ public interface PullRequestsService {
|
||||
|
||||
Page<PullRequest> getAll(@NonNull Pagination pagination);
|
||||
|
||||
List<PullRequest> getAllByAuthor(@NonNull String login, @NonNull LocalDateTime dateFrom, @NonNull LocalDateTime dateTo);
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
package com.tsc.bitbucketbot.service;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
public interface ReportService {
|
||||
|
||||
String generateReport(@NonNull String login);
|
||||
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@ -72,7 +73,7 @@ public class PullRequestsServiceImpl implements PullRequestsService {
|
||||
|
||||
@Override
|
||||
public Set<Long> getAllId() {
|
||||
return pullRequestsRepository.getAllIds();
|
||||
return pullRequestsRepository.findAllIds();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -80,4 +81,10 @@ public class PullRequestsServiceImpl implements PullRequestsService {
|
||||
return pullRequestsRepository.findAll(PageRequest.of(pagination.getPage(), pagination.getSize()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PullRequest> getAllByAuthor(@NonNull String login, @NonNull LocalDateTime dateFrom, @NonNull LocalDateTime dateTo) {
|
||||
return pullRequestsRepository.findAllByAuthorAndDateBetween(login, dateFrom, dateTo);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
package com.tsc.bitbucketbot.service.impl;
|
||||
|
||||
import com.tsc.bitbucketbot.service.ReportService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ReportServiceImpl implements ReportService {
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.tsc.bitbucketbot.service.impl;
|
||||
|
||||
import com.tsc.bitbucketbot.domain.entity.PullRequest;
|
||||
import com.tsc.bitbucketbot.service.PullRequestsService;
|
||||
import com.tsc.bitbucketbot.service.ReportService;
|
||||
import com.tsc.bitbucketbot.utils.Smile;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ReportServiceSimple implements ReportService {
|
||||
|
||||
private final PullRequestsService pullRequestsService;
|
||||
|
||||
@Override
|
||||
public String generateReport(@NonNull String login) {
|
||||
final LocalDateTime now = LocalDateTime.now();
|
||||
final Map<LocalDate, List<PullRequest>> prByDay = pullRequestsService.getAllByAuthor(login, now.minusDays(7L), now).stream()
|
||||
.collect(Collectors.groupingBy(pullRequest -> pullRequest.getUpdateDate().toLocalDate()));
|
||||
return generateMessage(prByDay).orElse("Кажется эту неделю ты не работал");
|
||||
}
|
||||
|
||||
private Optional<String> generateMessage(@NonNull Map<LocalDate, List<PullRequest>> prByDay) {
|
||||
if (!prByDay.isEmpty()) {
|
||||
final StringBuilder message = new StringBuilder("Твой отчет на эту неделю:").append(Smile.TWO_BR);
|
||||
for (Map.Entry<LocalDate, List<PullRequest>> entry : prByDay.entrySet()) {
|
||||
message.append(dayOfWeek(entry.getKey())).append(": ");
|
||||
for (PullRequest pullRequest : entry.getValue()) {
|
||||
message.append(" - ").append(pullRequest.getName()).append(Smile.BR)
|
||||
.append(" --").append(pullRequest.getDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private String dayOfWeek(LocalDate date) {
|
||||
switch (date.getDayOfWeek()) {
|
||||
case MONDAY:
|
||||
return "Понедельник";
|
||||
case SUNDAY:
|
||||
return "Воскресенье";
|
||||
case TUESDAY:
|
||||
return "Вторник";
|
||||
case SATURDAY:
|
||||
return "Суббота";
|
||||
case THURSDAY:
|
||||
return "Четверг";
|
||||
case WEDNESDAY:
|
||||
return "Среда";
|
||||
case FRIDAY:
|
||||
return "Пятница";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
29
src/main/resources/application-dev.yaml
Normal file
29
src/main/resources/application-dev.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/bitbucket_bot
|
||||
username: postgres
|
||||
driver-class-name: org.postgresql.Driver
|
||||
password: 121314Ma
|
||||
liquibase:
|
||||
change-log: classpath:liquibase/change-log.xml
|
||||
jpa:
|
||||
show-sql: false
|
||||
hibernate:
|
||||
ddl-auto: none
|
||||
database-platform: org.hibernate.dialect.PostgreSQLDialect
|
||||
properties:
|
||||
hibernate:
|
||||
jdbc:
|
||||
lob:
|
||||
non_contextual_creation: true
|
||||
bitbucketbot:
|
||||
init:
|
||||
start-comment-id:
|
||||
server-send:
|
||||
url: http://188.225.35.149:8080/api/send
|
||||
bitbucket:
|
||||
token: Nzg5NjUyNDQwMzk2OlA+6naQz02+GxOG0Q9li/jnsn7E
|
||||
url-pull-request-open: http://192.168.236.164:7990/rest/api/1.0/dashboard/pull-requests?limit=150&state=OPEN
|
||||
url-pull-request-close: http://192.168.236.164:7990/rest/api/1.0/dashboard/pull-requests?limit=150&closedSince=86400
|
||||
url-pull-request-comment: http://192.168.236.164:7990/rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId}
|
||||
url-pull-request: http://192.168.236.164:7990/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/overview
|
27
src/main/resources/application-prod.yaml
Normal file
27
src/main/resources/application-prod.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/bitbucket_bot
|
||||
username: postgres
|
||||
driver-class-name: org.postgresql.Driver
|
||||
password:
|
||||
liquibase:
|
||||
change-log: classpath:liquibase/change-log.xml
|
||||
jpa:
|
||||
show-sql: false
|
||||
hibernate:
|
||||
ddl-auto: none
|
||||
database-platform: org.hibernate.dialect.PostgreSQLDialect
|
||||
properties:
|
||||
hibernate:
|
||||
jdbc:
|
||||
lob:
|
||||
non_contextual_creation: true
|
||||
bitbucketbot:
|
||||
server-send:
|
||||
url: http://188.225.35.149:8080/api/send
|
||||
bitbucket:
|
||||
token: Nzg5NjUyNDQwMzk2OlA+6naQz02+GxOG0Q9li/jnsn7E
|
||||
url-pull-request-open: http://localhost:7990/rest/api/1.0/dashboard/pull-requests?limit=150&state=OPEN
|
||||
url-pull-request-close: http://localhost:7990/rest/api/1.0/dashboard/pull-requests?limit=150&closedSince=86400
|
||||
url-pull-request-comment: http://localhost:7990/rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId}
|
||||
url-pull-request: http://localhost:7990/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/overview
|
@ -1,29 +1,2 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/bitbucket_bot
|
||||
username: postgres
|
||||
driver-class-name: org.postgresql.Driver
|
||||
password:
|
||||
liquibase:
|
||||
change-log: classpath:liquibase/change-log.xml
|
||||
jpa:
|
||||
show-sql: false
|
||||
hibernate:
|
||||
ddl-auto: none
|
||||
database-platform: org.hibernate.dialect.PostgreSQLDialect
|
||||
properties:
|
||||
hibernate:
|
||||
jdbc:
|
||||
lob:
|
||||
non_contextual_creation: true
|
||||
bitbucketbot:
|
||||
server-send:
|
||||
url: http://188.225.35.149:8080/api/send
|
||||
bitbucket:
|
||||
token: Nzg5NjUyNDQwMzk2OlA+6naQz02+GxOG0Q9li/jnsn7E
|
||||
url-pull-request-open: http://localhost:7990/rest/api/1.0/dashboard/pull-requests?limit=150&state=OPEN
|
||||
url-pull-request-close: http://localhost:7990/rest/api/1.0/dashboard/pull-requests?limit=150&closedSince=86400
|
||||
url-pull-request-comment: http://localhost:7990/rest/api/1.0/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId}
|
||||
url-pull-request: http://localhost:7990/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/overview
|
||||
server:
|
||||
port: 8018
|
||||
|
@ -18,7 +18,7 @@
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
Loading…
Reference in New Issue
Block a user