Compare commits

...

36 Commits

Author SHA1 Message Date
0e1e7c6cad
Уведомление об изменении ревьюверов
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-04 00:16:56 +03:00
625c59f163
Уведомление о возникновении конфликта 2024-09-04 00:09:26 +03:00
07b5687cf2
Добавлено уведомление об отсутствии ревьюверов 2024-09-03 23:51:44 +03:00
e5727f2b30
Групповые уведомления о пайплайнах 2024-09-03 23:21:17 +03:00
936490de23
Первая версия групповых уведомлений 2024-09-03 22:51:03 +03:00
c11a3148be
fixed 2024-09-02 21:24:36 +03:00
0704231d48
Добавил сообщение об ошибке, при отсутствии доступа к Gitlab api
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-27 19:26:26 +03:00
4d8a48497e
cicd update java v21
Some checks failed
continuous-integration/drone/push Build is failing
2024-08-27 18:56:14 +03:00
e844942973
Добавлена поддержка апрувов 2024-08-26 20:21:30 +03:00
214c0401ac
Изменение подхода к уведомлениям 2024-08-26 18:01:23 +03:00
57c787bf01
Изменение подхода к уведомлениям 2024-08-26 17:56:18 +03:00
fb8a7e8a53
Переход на gitlab-sdk 2024-08-23 23:37:36 +03:00
58fed44e04
Рефакторинг урлов гитлаба 2024-08-23 22:25:17 +03:00
5c8ddb8e08
Вынес вызовы к gitlab в отдельный модуль 2024-08-23 22:00:46 +03:00
d2e1fdbfcb
Рефакторинг по пакетам 2024-08-23 20:41:33 +03:00
7b0019f4fe
Merge branch 'approvals' into develop 2024-08-23 20:31:48 +03:00
160bd79fd7
[maven-release-plugin] prepare for next development iteration
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-23 20:26:20 +03:00
47f93114f3
[maven-release-plugin] prepare release v.1.1.0
All checks were successful
continuous-integration/drone/tag Build is passing
2024-08-23 20:26:19 +03:00
d6512a695c
fix format notify PipelineNotify 2024-08-23 20:25:36 +03:00
050ab4eaa7
cicd fixed
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-22 12:54:37 +03:00
f58258107f
Исправление экранирования названия уведомления. Исправление парсинга старых пайпов.
Some checks failed
continuous-integration/drone/push Build is failing
2024-08-22 12:15:38 +03:00
aa4f1ebb8a
<godfather.telegram.version>1.0.0</godfather.telegram.version>
Some checks failed
continuous-integration/drone/push Build is failing
2024-08-21 17:06:45 +03:00
367af637a7
Merge branch 'master' into develop 2024-08-21 17:05:49 +03:00
5c4dca0d57
Merge branch 'develop' into approvals
# Conflicts:
#	gitlab-app/src/main/resources/liquibase/v.2.0.0/changelog.xml
2024-08-21 16:57:45 +03:00
60a71731bd
fixed Dockerfile 2024-08-21 16:57:04 +03:00
6eab8daf31
update .drone.yml
Some checks failed
continuous-integration/drone/push Build is failing
2024-08-21 16:50:44 +03:00
7da67a11f0
fixes 2024-08-21 16:34:30 +03:00
39f364c22b
fixes 2024-02-06 09:39:33 +03:00
8f48af4767
Рефакторинг 2024-01-27 09:41:32 +03:00
67e14ca2b5
Удаление отправки статистики 2024-01-26 13:37:48 +03:00
ef162456f1
Исправление форматирования сообщений 2024-01-22 11:05:43 +03:00
76ffd566af
Добавление функционала апрува 2024-01-22 11:05:31 +03:00
905e13053e
Исправление форматирования сообщений 2024-01-19 20:41:34 +03:00
4eb678ce53
update dependencies 2024-01-19 20:23:30 +03:00
2c19256fbe
add ton 2023-03-16 00:04:34 +03:00
32f427a6a2
[maven-release-plugin] prepare for next development iteration
All checks were successful
continuous-integration/drone/push Build is passing
2023-03-06 02:59:07 +03:00
145 changed files with 3098 additions and 2725 deletions

View File

@ -3,13 +3,17 @@ kind: pipeline
type: docker
name: develop build
image_pull_secrets:
- DOCKER_AUTH
trigger:
branch:
- develop
services:
- name: docker
image: docker:20.10.22-dind-alpine3.17
# https://hub.docker.com/r/library/docker
image: hub.docker.struchkov.dev/docker:27.1.2-dind-alpine3.20
privileged: true
volumes:
- name: dockersock
@ -24,7 +28,8 @@ volumes:
steps:
- name: create jar
image: maven:3.8.6-eclipse-temurin-17
# https://hub.docker.com/_/maven
image: hub.docker.struchkov.dev/maven:3.9-eclipse-temurin-22-alpine
volumes:
- name: m2
path: /root/.m2/repository
@ -32,26 +37,19 @@ steps:
- mvn -U clean package
- name: docker publish develop
image: upagge/docker-buildx:latest
image: docker.struchkov.dev/docker-buildx:latest
environment:
STRUCHKOV_DOCKER_REGISTRY_TOKEN:
from_secret: STRUCHKOV_DOCKER_REGISTRY_TOKEN
STRUCHKOV_DOCKER_IMAGE_NAME:
from_secret: STRUCHKOV_DOCKER_IMAGE_NAME
DOCKER_REGISTRY_TOKEN:
from_secret: DOCKER_REGISTRY_TOKEN
DOCKER_IMAGE_NAME:
from_secret: DOCKER_IMAGE_NAME
DOCKER_REGISTRY_USER:
from_secret: DOCKER_REGISTRY_USER
volumes:
- name: dockersock
path: /var/run
commands:
- echo "$STRUCHKOV_DOCKER_REGISTRY_TOKEN" | docker login git.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin
- echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.io --username $DOCKER_REGISTRY_USER --password-stdin
- echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin
- docker buildx create --use
- docker buildx build -f Dockerfile-develop --push --platform linux/amd64,linux/arm64/v8 -t "$DOCKER_IMAGE_NAME:develop" -t "git.struchkov.dev/$STRUCHKOV_DOCKER_IMAGE_NAME:develop" .
- docker buildx build -f Dockerfile-develop --push --platform linux/amd64,linux/arm64/v8 -t "docker.struchkov.dev/gitlab-notify:develop" .
---
@ -59,13 +57,17 @@ kind: pipeline
type: docker
name: release build
image_pull_secrets:
- DOCKER_AUTH
trigger:
ref:
- refs/tags/v.*.*.*
services:
- name: docker
image: docker:20.10.22-dind-alpine3.17
# https://hub.docker.com/r/library/docker
image: hub.docker.struchkov.dev/docker:27.1.2-dind-alpine3.20
privileged: true
volumes:
- name: dockersock
@ -80,7 +82,7 @@ volumes:
steps:
- name: create jar
image: maven:3.8.6-eclipse-temurin-17
image: hub.docker.struchkov.dev/maven:3.9-eclipse-temurin-22-alpine
volumes:
- name: m2
path: /root/.m2/repository
@ -88,122 +90,117 @@ steps:
- mvn -U clean package
- name: docker publish release
image: upagge/docker-buildx:latest
environment:
STRUCHKOV_DOCKER_REGISTRY_TOKEN:
from_secret: STRUCHKOV_DOCKER_REGISTRY_TOKEN
STRUCHKOV_DOCKER_IMAGE_NAME:
from_secret: STRUCHKOV_DOCKER_IMAGE_NAME
DOCKER_REGISTRY_TOKEN:
from_secret: DOCKER_REGISTRY_TOKEN
DOCKER_IMAGE_NAME:
from_secret: DOCKER_IMAGE_NAME
DOCKER_REGISTRY_USER:
from_secret: DOCKER_REGISTRY_USER
image: docker.struchkov.dev/docker-buildx:latest
volumes:
- name: dockersock
path: /var/run
environment:
DOCKER_REGISTRY_TOKEN:
from_secret: DOCKER_REGISTRY_TOKEN
DOCKER_REGISTRY_USER:
from_secret: DOCKER_REGISTRY_USER
commands:
- echo "$STRUCHKOV_DOCKER_REGISTRY_TOKEN" | docker login git.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin
- echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.io --username $DOCKER_REGISTRY_USER --password-stdin
- echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin
- docker buildx create --use
- docker buildx build --push --platform linux/amd64,linux/arm64/v8 -t "$DOCKER_IMAGE_NAME:latest" -t "$DOCKER_IMAGE_NAME:$DRONE_TAG" -t "git.struchkov.dev/$STRUCHKOV_DOCKER_IMAGE_NAME:latest" -t "git.struchkov.dev/$STRUCHKOV_DOCKER_IMAGE_NAME:$DRONE_TAG" .
- docker buildx build --push --platform linux/amd64,linux/arm64/v8 -t "docker.struchkov.dev/gitlab-notify:latest" -t "docker.struchkov.dev/gitlab-notify:$DRONE_TAG" .
---
kind: pipeline
type: docker
name: create-develop-docs-site
#---
#kind: pipeline
#type: docker
#name: create-develop-docs-site
#
#trigger:
# branch:
# - develop
# - docs
#
#clone:
# disable: true
#
#steps:
#
# - name: build docs
# image: git.struchkov.dev/upagge/mkdocs-material-insiders:latest
# volumes:
# - name: mkdocs_cache
# path: ${DRONE_WORKSPACE}/documentation/ru/.cache
# environment:
# GIT_SSH:
# from_secret: GIT_SSH
# GIT_SSH_COMMAND: "ssh -i ~/.ssh/id_rsa -p 222"
# commands:
# - eval $(ssh-agent -s)
# - mkdir -p ~/.ssh
# - chmod 700 ~/.ssh
# - echo "$GIT_SSH" >> ~/.ssh/id_rsa
# - chmod 600 ~/.ssh/id_rsa
# - ssh-keyscan -p 222 git.struchkov.dev >> ~/.ssh/known_hosts
# - chmod 644 ~/.ssh/known_hosts
# - git config --global user.name "${DRONE_COMMIT_AUTHOR_NAME}"
# - git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}"
# - git clone ssh://git@git.struchkov.dev:222/Telegram-Bots/gitlab-notification.git .
# - git checkout $DRONE_COMMIT
# - cd documentation/ru
# - mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases develop
#
#image_pull_secrets:
# - DOCKER_AUTH
#
#volumes:
# - name: mkdocs_cache
# host:
# path: /drone/volume/mkdocs_cache/gitlab_notification/ru
#
#---
#kind: pipeline
#type: docker
#name: create-release-docs-site
#
#trigger:
# ref:
# - refs/tags/v.*.*.*
#
#clone:
# disable: true
#
#steps:
#
# - name: build docs
# image: git.struchkov.dev/upagge/mkdocs-material-insiders:latest
# volumes:
# - name: mkdocs_cache
# path: ${DRONE_WORKSPACE}/documentation/ru/.cache
# environment:
# GIT_SSH:
# from_secret: GIT_SSH
# GIT_SSH_COMMAND: "ssh -i ~/.ssh/id_rsa -p 222"
# commands:
# - eval $(ssh-agent -s)
# - mkdir -p ~/.ssh
# - chmod 700 ~/.ssh
# - echo "$GIT_SSH" >> ~/.ssh/id_rsa
# - chmod 600 ~/.ssh/id_rsa
# - ssh-keyscan -p 222 git.struchkov.dev >> ~/.ssh/known_hosts
# - chmod 644 ~/.ssh/known_hosts
# - git config --global user.name "${DRONE_COMMIT_AUTHOR_NAME}"
# - git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}"
# - git clone ssh://git@git.struchkov.dev:222/Telegram-Bots/gitlab-notification.git .
# - git checkout $DRONE_COMMIT
# - cd documentation/ru
# - mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases ${DRONE_TAG}
# - mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases latest
#
#image_pull_secrets:
# - DOCKER_AUTH
#
#volumes:
# - name: mkdocs_cache
# host:
# path: /drone/volume/mkdocs_cache/gitlab_notification\
trigger:
branch:
- develop
- docs
clone:
disable: true
steps:
- name: build docs
image: git.struchkov.dev/upagge/mkdocs-material-insiders:latest
volumes:
- name: mkdocs_cache
path: ${DRONE_WORKSPACE}/documentation/ru/.cache
environment:
GIT_SSH:
from_secret: GIT_SSH
GIT_SSH_COMMAND: "ssh -i ~/.ssh/id_rsa -p 222"
commands:
- eval $(ssh-agent -s)
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$GIT_SSH" >> ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ssh-keyscan -p 222 git.struchkov.dev >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- git config --global user.name "${DRONE_COMMIT_AUTHOR_NAME}"
- git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}"
- git clone ssh://git@git.struchkov.dev:222/Telegram-Bots/gitlab-notification.git .
- git checkout $DRONE_COMMIT
- cd documentation/ru
- mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases develop
image_pull_secrets:
- DOCKER_AUTH
volumes:
- name: mkdocs_cache
host:
path: /drone/volume/mkdocs_cache/gitlab_notification/ru
---
kind: pipeline
type: docker
name: create-release-docs-site
trigger:
ref:
- refs/tags/v.*.*.*
clone:
disable: true
steps:
- name: build docs
image: git.struchkov.dev/upagge/mkdocs-material-insiders:latest
volumes:
- name: mkdocs_cache
path: ${DRONE_WORKSPACE}/documentation/ru/.cache
environment:
GIT_SSH:
from_secret: GIT_SSH
GIT_SSH_COMMAND: "ssh -i ~/.ssh/id_rsa -p 222"
commands:
- eval $(ssh-agent -s)
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$GIT_SSH" >> ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ssh-keyscan -p 222 git.struchkov.dev >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- git config --global user.name "${DRONE_COMMIT_AUTHOR_NAME}"
- git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}"
- git clone ssh://git@git.struchkov.dev:222/Telegram-Bots/gitlab-notification.git .
- git checkout $DRONE_COMMIT
- cd documentation/ru
- mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases ${DRONE_TAG}
- mike deploy --prefix gitlab-notification/ru --branch docs-deploy --push --update-aliases latest
image_pull_secrets:
- DOCKER_AUTH
volumes:
- name: mkdocs_cache
host:
path: /drone/volume/mkdocs_cache/gitlab_notification
# drone sign --save Telegram-Bots/gitlab-notification
---
kind: signature
hmac: cf1bd0800e8f6bb49dae0a6c5f607676b87d5ee713f4203f4f1ed08a17f71f68
hmac: b0704e1ed6c75469f2197afa7de1fb6f4a96765bb00f766be8a3da6fc4a87267
...

View File

@ -1,4 +1,4 @@
FROM eclipse-temurin:17 as app-build
FROM eclipse-temurin:17 AS app-build
ENV RELEASE=17
WORKDIR /opt/build

View File

@ -1,4 +1,4 @@
FROM eclipse-temurin:17 as app-build
FROM eclipse-temurin:17 AS app-build
ENV RELEASE=17
WORKDIR /opt/build

View File

@ -4,7 +4,7 @@
<parent>
<groupId>dev.struchkov.bot.gitlab</groupId>
<artifactId>gitlab-bot</artifactId>
<version>1.0.0</version>
<version>2.0.0-SNAPSHOT</version>
</parent>
<artifactId>bot-context</artifactId>

View File

@ -1,6 +1,10 @@
package dev.struchkov.bot.gitlab.context.domain;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
@ -8,6 +12,9 @@ import lombok.Setter;
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class PersonInformation {
private String username;

View File

@ -21,7 +21,7 @@ public enum PipelineStatus {
SKIPPED("\uD83D\uDD18"),
MANUAL("\uD83D\uDD79"),
SCHEDULED("\uD83D\uDD52"),
NULL("\uD83C\uDD95");
NEW("\uD83C\uDD95");
private final String icon;

View File

@ -0,0 +1,51 @@
package dev.struchkov.bot.gitlab.context.domain.changed;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ApprovalChanged {
private Set<Person> newApproval;
private Set<Person> dontApprove;
public static Optional<ApprovalChanged> approvalChanged(List<Person> oldApproval, List<Person> newApproval) {
// Если списки одинаковы, возвращаем пустой Optional
if (Objects.equals(oldApproval, newApproval)) {
return Optional.empty();
}
// Преобразуем списки в множества для удобства вычислений
Set<Person> oldApprovalSet = new HashSet<>(oldApproval);
Set<Person> newApprovalSet = new HashSet<>(newApproval);
// Вычисляем новые одобрения: те, кто есть в новом списке, но нет в старом
final Set<Person> newApprovalSetOnly = new HashSet<>(newApprovalSet);
newApprovalSetOnly.removeAll(oldApprovalSet);
// Вычисляем тех, кто больше не одобряет: те, кто есть в старом списке, но нет в новом
final Set<Person> dontApproveSet = new HashSet<>(oldApprovalSet);
dontApproveSet.removeAll(newApprovalSet);
if (!newApprovalSetOnly.isEmpty() || !dontApproveSet.isEmpty()) {
return Optional.of(new ApprovalChanged(newApprovalSetOnly, dontApproveSet));
}
return Optional.empty();
}
}

View File

@ -1,11 +1,10 @@
package dev.struchkov.bot.gitlab.context.domain;
package dev.struchkov.bot.gitlab.context.domain.changed;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
import static dev.struchkov.haiti.utils.Checker.checkNull;
import java.util.Optional;
@Getter
@RequiredArgsConstructor
@ -18,18 +17,18 @@ public enum AssigneeChanged {
private final boolean changed;
public static AssigneeChanged valueOf(Long gitlabUserId, Person oldAssignee, Person newAssignee) {
if (checkNull(oldAssignee) && checkNotNull(newAssignee) && gitlabUserId.equals(newAssignee.getId())) {
public static AssigneeChanged valueOf(Long gitlabUserId, Optional<Person> oldAssignee, Optional<Person> newAssignee) {
if (oldAssignee.isEmpty() && newAssignee.isPresent() && gitlabUserId.equals(newAssignee.get().getId())) {
return AssigneeChanged.BECOME;
}
if (checkNotNull(oldAssignee) && checkNull(newAssignee) && gitlabUserId.equals(oldAssignee.getId())) {
if (oldAssignee.isPresent() && newAssignee.isEmpty() && gitlabUserId.equals(oldAssignee.get().getId())) {
return AssigneeChanged.DELETED;
}
if (checkNotNull(oldAssignee) && checkNotNull(newAssignee) && !oldAssignee.getId().equals(newAssignee.getId())) {
if (gitlabUserId.equals(oldAssignee.getId())) {
if (oldAssignee.isPresent() && newAssignee.isPresent() && !oldAssignee.get().getId().equals(newAssignee.get().getId())) {
if (gitlabUserId.equals(oldAssignee.get().getId())) {
return AssigneeChanged.DELETED;
}
if (gitlabUserId.equals(newAssignee.getId())) {
if (gitlabUserId.equals(newAssignee.get().getId())) {
return AssigneeChanged.BECOME;
}
return AssigneeChanged.NOT_AFFECT_USER;

View File

@ -1,4 +1,4 @@
package dev.struchkov.bot.gitlab.context.domain;
package dev.struchkov.bot.gitlab.context.domain.changed;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import lombok.Getter;

View File

@ -10,8 +10,12 @@ import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.List;
@ -25,7 +29,10 @@ import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@Getter
@Setter
@Entity
@NoArgsConstructor
@Builder(toBuilder = true)
@Table(name = "discussion")
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Discussion {

View File

@ -17,9 +17,14 @@ import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.ArrayList;
@ -35,12 +40,17 @@ import java.util.Set;
@Getter
@Setter
@Entity
@FieldNames(mode = {Mode.TABLE, Mode.SIMPLE})
@ToString(onlyExplicitlyIncluded = true)
@NoArgsConstructor
@Builder(toBuilder = true)
@Table(name = "merge_request")
@FieldNames(mode = {Mode.TABLE, Mode.SIMPLE})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class MergeRequest {
@Id
@ToString.Include
@Column(name = "id")
@EqualsAndHashCode.Include
private Long id;
@ -51,12 +61,16 @@ public class MergeRequest {
@Column(name = "project_id")
private Long projectId;
@ToString.Include
@Column(name = "title")
private String title;
@Column(name = "description")
private String description;
@Column(name = "milestone")
private String milestone;
@Enumerated(EnumType.STRING)
@Column(name = "state")
private MergeRequestState state;
@ -107,6 +121,17 @@ public class MergeRequest {
@Column(name = "is_reviewer")
private boolean userReviewer;
@OneToMany(
fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE}
)
@JoinTable(
name = "merge_request_approvals",
joinColumns = @JoinColumn(name = "merge_request_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "person_id", referencedColumnName = "id")
)
private List<Person> approvals = new ArrayList<>();
@ElementCollection
@CollectionTable(name = "merge_request_label", joinColumns = @JoinColumn(name = "merge_request_id"))
@Column(name = "label")

View File

@ -9,6 +9,7 @@ import jakarta.persistence.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @author upagge 14.01.2021
@ -16,9 +17,10 @@ import lombok.Setter;
@Entity
@Getter
@Setter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString
@Table(name = "person")
@FieldNames(mode = {Mode.TABLE, Mode.SIMPLE})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Person {
@Id

View File

@ -0,0 +1,31 @@
package dev.struchkov.bot.gitlab.context.domain.entity;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import dev.struchkov.haiti.utils.fieldconstants.domain.Mode;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Entity
@Getter
@Setter
@ToString
@Table(name = "person_telegram")
@FieldNames(mode = {Mode.TABLE, Mode.SIMPLE})
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class PersonTelegram {
@Id
@EqualsAndHashCode.Include
@Column(name = "id")
private Long id;
@Column(name = "telegram_username")
private String telegramUserName;
}

View File

@ -11,8 +11,12 @@ import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.LocalDateTime;
@ -25,8 +29,11 @@ import java.time.LocalDateTime;
@Getter
@Setter
@FieldNames
@NoArgsConstructor
@Table(name = "pipeline")
@Builder(toBuilder = true)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class Pipeline {
@Id

View File

@ -0,0 +1,19 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "newDiscussion")
public class NewDiscussionEvent {
private Discussion discussion;
}

View File

@ -0,0 +1,19 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "newMergeRequest")
public class NewMergeRequestEvent {
private MergeRequest mergeRequest;
}

View File

@ -0,0 +1,19 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "newPipeline")
public class NewPipelineEvent {
private Pipeline pipeline;
}

View File

@ -0,0 +1,19 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "newProject")
public class NewProjectEvent {
private Project project;
}

View File

@ -0,0 +1,20 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "updateDiscussion")
public class UpdateDiscussionEvent {
private Discussion oldDiscussion;
private Discussion newDiscussion;
}

View File

@ -0,0 +1,29 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.changed.ApprovalChanged;
import dev.struchkov.bot.gitlab.context.domain.changed.AssigneeChanged;
import dev.struchkov.bot.gitlab.context.domain.changed.ReviewerChanged;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.Optional;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "updateMergeRequest")
public class UpdateMergeRequestEvent {
private MergeRequest oldMergeRequest;
private MergeRequest newMergeRequest;
private AssigneeChanged assigneeChanged;
private ReviewerChanged reviewerChanged;
private Optional<ApprovalChanged> optApprovalChanged;
}

View File

@ -0,0 +1,20 @@
package dev.struchkov.bot.gitlab.context.domain.event;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor(staticName = "updatePipeline")
public class UpdatePipelineEvent {
private Pipeline oldPipeline;
private Pipeline newPipeline;
}

View File

@ -0,0 +1,5 @@
package dev.struchkov.bot.gitlab.context.domain.notify;
public interface GroupNotify extends Notify {
}

View File

@ -0,0 +1,5 @@
package dev.struchkov.bot.gitlab.context.domain.notify;
public interface PersonalNotify extends Notify{
}

View File

@ -1,13 +1,13 @@
package dev.struchkov.bot.gitlab.context.domain.notify.comment;
import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
@Getter
@FieldNames
public final class NewCommentNotify implements Notify {
public final class NewCommentPersonalNotify implements PersonalNotify {
public static final String TYPE = "NewCommentNotify";
@ -23,7 +23,7 @@ public final class NewCommentNotify implements Notify {
private final int numberNotes;
@Builder
public NewCommentNotify(
public NewCommentPersonalNotify(
String threadId,
String mergeRequestName,
String url,

View File

@ -0,0 +1,38 @@
package dev.struchkov.bot.gitlab.context.domain.notify.group.mr;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.group.mr.ConflictMrGroupNotifyFields.CLASS_NAME;
@Getter
@FieldNames
public class ConflictMrGroupNotify implements GroupNotify {
public static final String TYPE = CLASS_NAME;
protected final String sourceBranch;
protected final String projectName;
protected final String title;
protected final String url;
protected final String milestone;
protected final String authorTelegramUsername;
@Builder
public ConflictMrGroupNotify(String sourceBranch, String projectName, String title, String url, String milestone, String authorTelegramUsername) {
this.sourceBranch = sourceBranch;
this.projectName = projectName;
this.title = title;
this.url = url;
this.milestone = milestone;
this.authorTelegramUsername = authorTelegramUsername;
}
@Override
public String getType() {
return TYPE;
}
}

View File

@ -0,0 +1,51 @@
package dev.struchkov.bot.gitlab.context.domain.notify.group.mr;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import java.util.Map;
import static dev.struchkov.bot.gitlab.context.domain.notify.group.mr.NewMergeRequestGroupPersonalNotifyFields.CLASS_NAME;
@Getter
@FieldNames
public class NewMergeRequestGroupNotify implements GroupNotify {
public static final String TYPE = CLASS_NAME;
protected final Long mrId;
protected final String projectName;
protected final String title;
protected final String url;
protected final String milestone;
protected final String description;
protected final String author;
protected final String targetBranch;
protected final String sourceBranch;
protected final Map<Long, String> reviewerTelegramUsernames;
protected final Person assignee;
@Builder
public NewMergeRequestGroupNotify(Long mrId, String projectName, String title, String url, String milestone, String description, String author, String targetBranch, String sourceBranch, Map<Long, String> reviewerTelegramUsernames, Person assignee) {
this.mrId = mrId;
this.projectName = projectName;
this.title = title;
this.url = url;
this.milestone = milestone;
this.description = description;
this.author = author;
this.targetBranch = targetBranch;
this.sourceBranch = sourceBranch;
this.reviewerTelegramUsernames = reviewerTelegramUsernames;
this.assignee = assignee;
}
@Override
public String getType() {
return TYPE;
}
}

View File

@ -0,0 +1,41 @@
package dev.struchkov.bot.gitlab.context.domain.notify.group.mr;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
@Getter
@FieldNames
public class NoReviewerGroupNotify implements GroupNotify {
public static final String TYPE = "NoReviewersGroupNotify";
protected final String projectName;
protected final String title;
protected final String url;
protected final String milestone;
protected final String targetBranch;
protected final String sourceBranch;
protected final Person assignee;
protected final String ownerTelegramUsername;
@Builder
public NoReviewerGroupNotify(String projectName, String title, String url, String milestone, String targetBranch, String sourceBranch, Person assignee, String ownerTelegramUsername) {
this.projectName = projectName;
this.title = title;
this.url = url;
this.milestone = milestone;
this.targetBranch = targetBranch;
this.sourceBranch = sourceBranch;
this.assignee = assignee;
this.ownerTelegramUsername = ownerTelegramUsername;
}
@Override
public String getType() {
return TYPE;
}
}

View File

@ -0,0 +1,39 @@
package dev.struchkov.bot.gitlab.context.domain.notify.group.pipeline;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.group.pipeline.PipelineGroupNotifyFields.CLASS_NAME;
@Getter
@FieldNames
public class PipelineGroupNotify implements GroupNotify {
public static final String TYPE = CLASS_NAME;
private final String projectName;
private final String refName;
private final PipelineStatus oldStatus;
private final PipelineStatus newStatus;
private final String webUrl;
private final String ownerTelegramUsername;
@Builder
public PipelineGroupNotify(String projectName, String refName, PipelineStatus oldStatus, PipelineStatus newStatus, String webUrl, String ownerTelegramUsername) {
this.projectName = projectName;
this.refName = refName;
this.oldStatus = oldStatus;
this.newStatus = newStatus;
this.webUrl = webUrl;
this.ownerTelegramUsername = ownerTelegramUsername;
}
@Override
public String getType() {
return TYPE;
}
}

View File

@ -0,0 +1,42 @@
package dev.struchkov.bot.gitlab.context.domain.notify.mergerequest;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import java.util.Set;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ApprovalChangedMrPersonalNotifyFields.CLASS_NAME;
@Getter
@FieldNames
public class ApprovalChangedMrPersonalNotify extends MrPersonalNotify {
public static final String TYPE = CLASS_NAME;
private final Set<Person> newApproval;
private final Set<Person> dontApproval;
@Builder
public ApprovalChangedMrPersonalNotify(
Long mrId,
String projectName,
String title,
String url,
String milestone,
Set<Person> newApproval,
Set<Person> dontApproval
) {
super(mrId, projectName, title, url, milestone);
this.newApproval = newApproval;
this.dontApproval = dontApproval;
}
@Override
public String getType() {
return TYPE;
}
}

View File

@ -4,25 +4,26 @@ import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictMrNotifyFields.CLASS_NAME;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictMrPersonalNotifyFields.CLASS_NAME;
@Getter
@FieldNames
public class ConflictMrNotify extends MrNotify {
public class ConflictMrPersonalNotify extends MrPersonalNotify {
public static final String TYPE = CLASS_NAME;
private final String sourceBranch;
@Builder
private ConflictMrNotify(
private ConflictMrPersonalNotify(
Long mrId,
String name,
String url,
String projectKey,
String sourceBranch
String sourceBranch,
String milestone
) {
super(mrId, projectKey, name, url);
super(mrId, projectKey, name, url, milestone);
this.sourceBranch = sourceBranch;
}

View File

@ -4,25 +4,26 @@ import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictResolveMrNotifyFields.CLASS_NAME;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictResolveMrPersonalNotifyFields.CLASS_NAME;
@Getter
@FieldNames
public class ConflictResolveMrNotify extends MrNotify {
public class ConflictResolveMrPersonalNotify extends MrPersonalNotify {
public static final String TYPE = CLASS_NAME;
private final String sourceBranch;
@Builder
private ConflictResolveMrNotify(
private ConflictResolveMrPersonalNotify(
Long mrId,
String name,
String url,
String projectKey,
String sourceBranch
String sourceBranch,
String milestone
) {
super(mrId, projectKey, name, url);
super(mrId, projectKey, name, url, milestone);
this.sourceBranch = sourceBranch;
}

View File

@ -1,26 +1,29 @@
package dev.struchkov.bot.gitlab.context.domain.notify.mergerequest;
import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify;
import lombok.Getter;
@Getter
public abstract class MrNotify implements Notify {
public abstract class MrPersonalNotify implements PersonalNotify {
protected final Long mrId;
protected final String projectName;
protected final String title;
protected final String url;
protected final String milestone;
protected MrNotify(
protected MrPersonalNotify(
Long mrId,
String projectName,
String title,
String url
String url,
String milestone
) {
this.mrId = mrId;
this.projectName = projectName;
this.title = title;
this.url = url;
this.milestone = milestone;
}
}

View File

@ -12,7 +12,7 @@ import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrF
@Getter
@FieldNames
public class NewMrForAssignee extends NewMrNotify {
public class NewMrForAssignee extends NewMrPersonalNotify {
public static final String TYPE = CLASS_NAME;
@ -33,7 +33,8 @@ public class NewMrForAssignee extends NewMrNotify {
Set<String> labels,
@Singular List<String> reviewers,
String oldAssigneeName,
String newAssigneeName
String newAssigneeName,
String milestone
) {
super(
mrId,
@ -44,7 +45,8 @@ public class NewMrForAssignee extends NewMrNotify {
projectName,
targetBranch,
sourceBranch,
labels
labels,
milestone
);
this.reviewers = reviewers;
this.oldAssigneeName = oldAssigneeName;

View File

@ -10,7 +10,7 @@ import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrF
@Getter
@FieldNames
public class NewMrForReview extends NewMrNotify {
public class NewMrForReview extends NewMrPersonalNotify {
public static final String TYPE = CLASS_NAME;
@ -27,7 +27,8 @@ public class NewMrForReview extends NewMrNotify {
String targetBranch,
String sourceBranch,
Set<String> labels,
String assignee
String assignee,
String milestone
) {
super(
mrId,
@ -38,7 +39,8 @@ public class NewMrForReview extends NewMrNotify {
projectName,
targetBranch,
sourceBranch,
labels
labels,
milestone
);
this.assignee = assignee;
}

View File

@ -5,7 +5,7 @@ import lombok.Getter;
import java.util.Set;
@Getter
public abstract class NewMrNotify extends MrNotify {
public abstract class NewMrPersonalNotify extends MrPersonalNotify {
protected final String description;
protected final String author;
@ -13,7 +13,7 @@ public abstract class NewMrNotify extends MrNotify {
protected final String sourceBranch;
protected final Set<String> labels;
protected NewMrNotify(
protected NewMrPersonalNotify(
Long mrId,
String title,
String url,
@ -22,9 +22,10 @@ public abstract class NewMrNotify extends MrNotify {
String projectName,
String targetBranch,
String sourceBranch,
Set<String> labels
Set<String> labels,
String milestone
) {
super(mrId, projectName, title, url);
super(mrId, projectName, title, url, milestone);
this.description = description;
this.author = author;
this.targetBranch = targetBranch;

View File

@ -5,11 +5,11 @@ import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.StatusMrNotifyFields.CLASS_NAME;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.StatusMrPersonalNotifyFields.CLASS_NAME;
@Getter
@FieldNames
public class StatusMrNotify extends MrNotify {
public class StatusMrPersonalNotify extends MrPersonalNotify {
public static final String TYPE = CLASS_NAME;
@ -17,15 +17,16 @@ public class StatusMrNotify extends MrNotify {
private final MergeRequestState newStatus;
@Builder
private StatusMrNotify(
private StatusMrPersonalNotify(
Long mrId,
String name,
String url,
String projectName,
MergeRequestState oldStatus,
MergeRequestState newStatus
MergeRequestState newStatus,
String milestone
) {
super(mrId, projectName, name, url);
super(mrId, projectName, name, url, milestone);
this.oldStatus = oldStatus;
this.newStatus = newStatus;
}

View File

@ -4,11 +4,11 @@ import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.UpdateMrNotifyFields.CLASS_NAME;
import static dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.UpdateMrPersonalNotifyFields.CLASS_NAME;
@Getter
@FieldNames
public class UpdateMrNotify extends MrNotify {
public class UpdateMrPersonalNotify extends MrPersonalNotify {
public static final String TYPE = CLASS_NAME;
@ -20,7 +20,7 @@ public class UpdateMrNotify extends MrNotify {
private final String comment;
@Builder
private UpdateMrNotify(
private UpdateMrPersonalNotify(
Long mrId,
String name,
String url,
@ -30,9 +30,10 @@ public class UpdateMrNotify extends MrNotify {
Long allResolvedTasks,
Long personTasks,
Long personResolvedTasks,
String comment
String comment,
String milestone
) {
super(mrId, projectName, name, url);
super(mrId, projectName, name, url, milestone);
this.author = author;
this.allTasks = allTasks;
this.allResolvedTasks = allResolvedTasks;

View File

@ -1,12 +1,12 @@
package dev.struchkov.bot.gitlab.context.domain.notify.pipeline;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.pipeline.PipelineNotifyFields.CLASS_NAME;
import static dev.struchkov.bot.gitlab.context.domain.notify.pipeline.PipelinePersonalNotifyFields.CLASS_NAME;
/**
* @author upagge 17.01.2021
@ -14,28 +14,25 @@ import static dev.struchkov.bot.gitlab.context.domain.notify.pipeline.PipelineNo
//TODO [16.12.2022|uPagge]: Нужно реализовать заполнение projectName
@Getter
@FieldNames
public final class PipelineNotify implements Notify {
public final class PipelinePersonalNotify implements PersonalNotify {
public static final String TYPE = CLASS_NAME;
private final Long projectId;
private final Long pipelineId;
private final String projectName;
private final String refName;
private final PipelineStatus oldStatus;
private final PipelineStatus newStatus;
private final String webUrl;
@Builder
public PipelineNotify(
Long projectId,
Long pipelineId,
public PipelinePersonalNotify(
String projectName,
String refName,
PipelineStatus oldStatus,
PipelineStatus newStatus,
String webUrl
) {
this.projectId = projectId;
this.pipelineId = pipelineId;
this.projectName = projectName;
this.refName = refName;
this.oldStatus = oldStatus;
this.newStatus = newStatus;

View File

@ -1,18 +1,18 @@
package dev.struchkov.bot.gitlab.context.domain.notify.project;
import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify;
import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.project.NewProjectNotifyFields.CLASS_NAME;
import static dev.struchkov.bot.gitlab.context.domain.notify.project.NewProjectPersonalNotifyFields.CLASS_NAME;
/**
* @author upagge 15.01.2021
*/
@Getter
@FieldNames
public final class NewProjectNotify implements Notify {
public final class NewProjectPersonalNotify implements PersonalNotify {
public static final String TYPE = CLASS_NAME;
@ -25,7 +25,7 @@ public final class NewProjectNotify implements Notify {
private final String httpUrlToRepo;
@Builder
public NewProjectNotify(
public NewProjectPersonalNotify(
Long projectId,
String projectName,
String projectUrl,

View File

@ -8,14 +8,14 @@ import lombok.Singular;
import java.util.List;
import static dev.struchkov.bot.gitlab.context.domain.notify.task.DiscussionNewNotifyFields.CLASS_NAME;
import static dev.struchkov.bot.gitlab.context.domain.notify.task.DiscussionNewPersonalNotifyFields.CLASS_NAME;
/**
* @author upagge 10.09.2020
*/
@Getter
@FieldNames
public class DiscussionNewNotify extends ThreadNotify {
public class DiscussionNewPersonalNotify extends ThreadPersonalNotify {
public static final String TYPE = CLASS_NAME;
@ -23,7 +23,7 @@ public class DiscussionNewNotify extends ThreadNotify {
private final List<Pair<String, String>> notes;
@Builder
public DiscussionNewNotify(
public DiscussionNewPersonalNotify(
String threadId,
String mergeRequestName,
String authorName,

View File

@ -4,14 +4,14 @@ import dev.struchkov.haiti.utils.fieldconstants.annotation.FieldNames;
import lombok.Builder;
import lombok.Getter;
import static dev.struchkov.bot.gitlab.context.domain.notify.task.ThreadCloseNotifyFields.CLASS_NAME;
import static dev.struchkov.bot.gitlab.context.domain.notify.task.ThreadClosePersonalNotifyFields.CLASS_NAME;
/**
* @author upagge 10.09.2020
*/
@Getter
@FieldNames
public class ThreadCloseNotify extends ThreadNotify {
public class ThreadClosePersonalNotify extends ThreadPersonalNotify {
public static final String TYPE = CLASS_NAME;
@ -21,7 +21,7 @@ public class ThreadCloseNotify extends ThreadNotify {
private final String messageLastNote;
@Builder
protected ThreadCloseNotify(
protected ThreadClosePersonalNotify(
String mergeRequestName,
String authorName,
String url,

View File

@ -1,17 +1,17 @@
package dev.struchkov.bot.gitlab.context.domain.notify.task;
import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify;
import lombok.Getter;
@Getter
public abstract class ThreadNotify implements Notify {
public abstract class ThreadPersonalNotify implements PersonalNotify {
protected final String mergeRequestName;
protected final String authorName;
protected final String url;
protected final String messageTask;
protected ThreadNotify(
protected ThreadPersonalNotify(
String mergeRequestName,
String authorName,
String url,

View File

@ -0,0 +1,17 @@
package dev.struchkov.bot.gitlab.context.prop;
import lombok.Getter;
import lombok.Setter;
/**
* Основные настройки приложения.
*
* @author upagge 11.10.2020
*/
@Getter
@Setter
public class AppProperty {
private String version;
}

View File

@ -0,0 +1,15 @@
package dev.struchkov.bot.gitlab.context.prop;
import lombok.Getter;
import lombok.Setter;
import java.util.Optional;
@Getter
@Setter
public class GroupNotifyProperty {
private Optional<String> chatId;
private Optional<Integer> threadId;
}

View File

@ -0,0 +1,16 @@
package dev.struchkov.bot.gitlab.context.prop;
import lombok.Getter;
import lombok.Setter;
/**
* @author upagge 15.01.2021
*/
@Getter
@Setter
public class PersonProperty {
private String telegramId;
}

View File

@ -1,6 +1,7 @@
package dev.struchkov.bot.gitlab.context.repository;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.PersonTelegram;
import java.util.List;
import java.util.Optional;
@ -17,4 +18,8 @@ public interface PersonRepository {
List<Person> findAllById(Set<Long> personIds);
Optional<PersonTelegram> findTelegramInfoById(Long personId);
List<PersonTelegram> findAllTelegramInfoByIds(Set<Long> personIds);
}

View File

@ -1,11 +1,13 @@
package dev.struchkov.bot.gitlab.context.service;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify;
import lombok.NonNull;
import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
@FunctionalInterface
public interface MessageSendService {
void send(@NonNull Notify notify);
void send(@NonNull PersonalNotify notify);
void send(@NonNull GroupNotify notify);
}

View File

@ -1,15 +1,18 @@
package dev.struchkov.bot.gitlab.context.service;
import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify;
/**
* Сервис по работе с изменениями в битбакете.
*
* @author upagge
* @see Notify
* @see PersonalNotify
*/
public interface NotifyService {
<T extends Notify> void send(T notify);
<T extends PersonalNotify> void send(T notify);
<T extends GroupNotify> void send(T notify);
}

View File

@ -5,6 +5,8 @@ import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import lombok.NonNull;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
@ -14,12 +16,14 @@ public interface PersonService {
Person create(@NonNull Person person);
Person update(@NonNull Person person);
Person getByIdOrThrown(@NonNull Long personId);
ExistContainer<Person, Long> existsById(Set<Long> personIds);
List<Person> createAll(List<Person> newPersons);
Optional<String> getTelegramUsernamesByPersonIds(Long personId);
Map<Long, String> getTelegramUsernamesByPersonIds(Set<Long> personIds);
}

View File

@ -19,9 +19,11 @@ public class Icons {
public static final String GREEN_CIRCLE = "\uD83D\uDFE2";
public static final String PEN = "✏️";
public static final String ASSIGNEE = "\uD83C\uDFA9";
public static final String APPROVAL = "\uD83D\uDC4D";
public static final String BUILD = "⚙️";
public static final String LINK = "\uD83D\uDD17";
public static final String REVIEWER = "\uD83D\uDD0E";
public static final String MILESTONE = "\uD83C\uDFAF";
public static final String PROJECT = "\uD83C\uDFD7";
public static final String DISABLE_NOTIFY = "\uD83D\uDD15";
public static final String YES = "";

View File

@ -4,7 +4,7 @@
<parent>
<groupId>dev.struchkov.bot.gitlab</groupId>
<artifactId>gitlab-bot</artifactId>
<version>1.0.0</version>
<version>2.0.0-SNAPSHOT</version>
</parent>
<artifactId>bot-core</artifactId>
@ -34,6 +34,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -52,11 +56,6 @@
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>dev.struchkov.bot.gitlab</groupId>
<artifactId>gitlab-sdk</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@ -64,10 +63,12 @@
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<groupId>dev.struchkov.sdk.gitlab</groupId>
<artifactId>gitlab-sdk-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>

View File

@ -1,16 +0,0 @@
package dev.struchkov.bot.gitlab.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ForkJoinPool;
@Configuration
public class CoreConfig {
@Bean("parserPool")
public ForkJoinPool parserPool() {
return new ForkJoinPool(4);
}
}

View File

@ -1,21 +0,0 @@
package dev.struchkov.bot.gitlab.core.config.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* Основные настройки приложения.
*
* @author upagge 11.10.2020
*/
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "gitlab-bot")
public class AppProperty {
private String version;
}

View File

@ -1,71 +0,0 @@
package dev.struchkov.bot.gitlab.core.config.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* Данные необходимые для взаимодействия с API GitLab.
*
* @author upagge [31.01.2020]
*/
@Getter
@Setter
@Component
@ConfigurationProperties("gitlab-bot.gitlab")
public class GitlabProperty {
private String baseUrl;
private String replaceUrl;
private String usersUrl;
private String userUrl;
private String projectsUrl;
/**
* Адрес, по которому можно получить открытые MR
*/
private String openMergeRequestsUrl;
/**
* Адрес, по которому можно получить закрытые MR
*/
private String closeMergeRequestsUrl;
/**
* Адрес, по которому можно получить комментарии к MR
*/
private String commentsOfMergeRequestUrl;
/**
* Адрес MR
*/
private String mergeRequestUrl;
private String projectAddUrl;
private String noteUrl;
private String notesOfMergeRequestUrl;
private String pipelinesUrl;
private String pipelineUrl;
private String lastCommitOfMergeRequestUrl;
private String newNoteUrl;
/**
* Адрес дискуссий для MR
*/
private String discussionsUrl;
private String discussionUrl;
}

View File

@ -1,21 +0,0 @@
package dev.struchkov.bot.gitlab.core.config.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author upagge 15.01.2021
*/
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "gitlab-bot.person")
public class PersonProperty {
private String token;
private String telegramId;
}

View File

@ -1,7 +1,7 @@
package dev.struchkov.bot.gitlab.core.service.convert;
package dev.struchkov.bot.gitlab.core.convert;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import dev.struchkov.bot.gitlab.sdk.domain.DiscussionJson;
import dev.struchkov.sdk.gitlab.schema.note.DiscussionJson;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

View File

@ -1,11 +1,11 @@
package dev.struchkov.bot.gitlab.core.service.convert;
package dev.struchkov.bot.gitlab.core.convert;
import dev.struchkov.bot.gitlab.context.domain.MergeRequestState;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.sdk.domain.MergeRequestJson;
import dev.struchkov.bot.gitlab.sdk.domain.MergeRequestStateJson;
import dev.struchkov.bot.gitlab.sdk.domain.PersonJson;
import dev.struchkov.sdk.gitlab.schema.common.PersonJson;
import dev.struchkov.sdk.gitlab.schema.mergerequest.MergeRequestJson;
import dev.struchkov.sdk.gitlab.schema.mergerequest.MergeRequestStateJson;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
@ -43,6 +43,10 @@ public class MergeRequestJsonConverter implements Converter<MergeRequestJson, Me
convertLabels(mergeRequest, source.getLabels());
convertReviewers(mergeRequest, source.getReviewers());
if (checkNotNull(source.getMilestone())) {
mergeRequest.setMilestone(source.getMilestone().getTitle());
}
if (checkNotNull(source.getAssignee())) {
mergeRequest.setAssignee(convertPerson.convert(source.getAssignee()));
}

View File

@ -1,7 +1,7 @@
package dev.struchkov.bot.gitlab.core.service.convert;
package dev.struchkov.bot.gitlab.core.convert;
import dev.struchkov.bot.gitlab.context.domain.entity.Note;
import dev.struchkov.bot.gitlab.sdk.domain.NoteJson;
import dev.struchkov.sdk.gitlab.schema.note.NoteJson;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

View File

@ -1,7 +1,7 @@
package dev.struchkov.bot.gitlab.core.service.convert;
package dev.struchkov.bot.gitlab.core.convert;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.sdk.domain.PersonJson;
import dev.struchkov.sdk.gitlab.schema.common.PersonJson;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

View File

@ -1,9 +1,9 @@
package dev.struchkov.bot.gitlab.core.service.convert;
package dev.struchkov.bot.gitlab.core.convert;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import dev.struchkov.bot.gitlab.sdk.domain.PipelineJson;
import dev.struchkov.bot.gitlab.sdk.domain.PipelineStatusJson;
import dev.struchkov.sdk.gitlab.schema.pipeline.PipelineJson;
import dev.struchkov.sdk.gitlab.schema.pipeline.PipelineStatusJson;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

View File

@ -1,7 +1,7 @@
package dev.struchkov.bot.gitlab.core.service.convert;
package dev.struchkov.bot.gitlab.core.convert;
import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import dev.struchkov.bot.gitlab.sdk.domain.ProjectJson;
import dev.struchkov.sdk.gitlab.schema.repository.ProjectJson;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

View File

@ -1,35 +1,23 @@
package dev.struchkov.bot.gitlab.core.service.impl;
package dev.struchkov.bot.gitlab.core.handler;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequestForDiscussion;
import dev.struchkov.bot.gitlab.context.domain.entity.Note;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.notify.comment.NewCommentNotify;
import dev.struchkov.bot.gitlab.context.domain.event.NewDiscussionEvent;
import dev.struchkov.bot.gitlab.context.domain.event.UpdateDiscussionEvent;
import dev.struchkov.bot.gitlab.context.domain.notify.comment.NewCommentPersonalNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel;
import dev.struchkov.bot.gitlab.context.domain.notify.task.DiscussionNewNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.task.ThreadCloseNotify;
import dev.struchkov.bot.gitlab.context.repository.DiscussionRepository;
import dev.struchkov.bot.gitlab.context.domain.notify.task.DiscussionNewPersonalNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.task.ThreadClosePersonalNotify;
import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.bot.gitlab.context.service.NotifyService;
import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
import dev.struchkov.bot.gitlab.core.utils.StringUtils;
import dev.struchkov.haiti.utils.container.Pair;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -42,95 +30,100 @@ import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel.NOTIFY_WITH_CONTEXT;
import static dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel.WITHOUT_NOTIFY;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
import static dev.struchkov.haiti.utils.Checker.checkNull;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
/**
* Сервис для работы с дискуссиями.
*
* @author upagge 11.02.2021
*/
@Slf4j
@Service
@Component
@RequiredArgsConstructor
public class DiscussionServiceImpl implements DiscussionService {
public class DiscussionHandler {
protected static final Pattern PATTERN = Pattern.compile("@[\\w]+");
private final OkHttpClient client = new OkHttpClient();
private final DiscussionRepository repository;
private final NotifyService notifyService;
private final AppSettingService settingService;
private final PersonInformation personInformation;
private final GitlabProperty gitlabProperty;
private final PersonProperty personProperty;
private final AppSettingService settingService;
private final DiscussionService discussionService;
@Override
@Transactional
public Discussion create(@NonNull Discussion discussion) {
private final NotifyService notifyService;
@EventListener
public void newDiscussionEvent(NewDiscussionEvent event) {
final Discussion discussion = event.getDiscussion();
final List<Note> notes = discussion.getNotes();
final DiscussionLevel levelDiscussionNotify = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(levelDiscussionNotify)) {
discussion.setNotification(true);
if (isNeedNotifyNewNote(discussion)) {
notifyNewThread(discussion);
} else {
notes.forEach(note -> notifyAboutPersonalAnswer(discussion, note));
}
if (isNeedNotifyNewNote(discussion)) {
notifyNewThread(discussion);
} else {
discussion.setNotification(false);
notes.forEach(note -> notifyAboutPersonalAnswer(discussion, note));
}
final boolean resolved = discussion.getNotes().stream()
.allMatch(note -> note.isResolvable() && note.getResolved());
discussion.setResolved(resolved);
return repository.save(discussion);
}
@Override
@Transactional
public Discussion update(@NonNull Discussion discussion) {
final Discussion oldDiscussion = repository.findById(discussion.getId())
.orElseThrow(notFoundException("Дискуссия не найдена"));
@EventListener
public void updateDiscussionHandle(UpdateDiscussionEvent event) {
final Discussion oldDiscussion = event.getOldDiscussion();
final Discussion newDiscussion = event.getNewDiscussion();
discussion.setResponsible(oldDiscussion.getResponsible());
discussion.setMergeRequest(oldDiscussion.getMergeRequest());
discussion.setNotification(oldDiscussion.isNotification());
if (oldDiscussion.isNotification()) {
final Map<Long, Note> oldNoteMap = oldDiscussion
.getNotes().stream()
.collect(Collectors.toMap(Note::getId, n -> n));
final Person responsiblePerson = discussion.getResponsible();
if (checkNotNull(responsiblePerson)) {
for (Note note : discussion.getNotes()) {
if (responsiblePerson.getId().equals(note.getAuthor().getId())) {
note.setAuthor(responsiblePerson);
}
final Person resolvedBy = note.getResolvedBy();
if (checkNotNull(resolvedBy)) {
if (responsiblePerson.getId().equals(resolvedBy.getId())) {
note.setResolvedBy(responsiblePerson);
// Пользователь участвовал в обсуждении
final boolean userParticipatedInDiscussion = oldDiscussion.getNotes().stream()
.anyMatch(note -> personInformation.getId().equals(note.getAuthor().getId()));
final Note threadFirstNote = newDiscussion.getFirstNote();
if (TRUE.equals(newDiscussion.getResolved())) {
notifyAboutCloseThread(threadFirstNote, oldNoteMap.get(threadFirstNote.getId()), newDiscussion.getLastNote());
}
for (Note newNote : newDiscussion.getNotes()) {
final Long newNoteId = newNote.getId();
if (!oldNoteMap.containsKey(newNoteId)) {
if (userParticipatedInDiscussion) {
notifyAboutNewAnswer(newDiscussion, newNote);
} else {
notifyAboutPersonalAnswer(newDiscussion, newNote);
}
}
}
}
}
final boolean resolved = discussion.getNotes().stream()
.allMatch(note -> note.isResolvable() && note.getResolved());
discussion.setResolved(resolved);
/**
* <p>Уведомляет пользователя, если появился новый комментарий</p>
*/
private void notifyNewThread(Discussion discussion) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(discussionLevel)) {
final Note firstNote = discussion.getFirstNote();
if (oldDiscussion.isNotification()) {
notifyUpdateNote(oldDiscussion, discussion);
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest();
final DiscussionNewPersonalNotify.DiscussionNewPersonalNotifyBuilder messageBuilder = DiscussionNewPersonalNotify.builder()
.url(firstNote.getWebUrl())
.threadId(discussion.getId())
.mergeRequestName(mergeRequest.getTitle())
.authorName(firstNote.getAuthor().getName());
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final List<Note> notes = discussion.getNotes();
messageBuilder
.discussionMessage(firstNote.getBody());
if (notes.size() > 1) {
for (int i = 1; i < notes.size(); i++) {
final Note note = notes.get(i);
messageBuilder.note(
new Pair<>(note.getAuthor().getName(), note.getBody())
);
}
}
}
notifyService.send(messageBuilder.build());
}
return repository.save(discussion);
}
private boolean isNeedNotifyNewNote(Discussion discussion) {
@ -142,38 +135,80 @@ public class DiscussionServiceImpl implements DiscussionService {
&& FALSE.equals(firstNote.getResolved()); // Комментарий не отмечен как решенный
}
@Override
public List<Discussion> updateAll(@NonNull List<Discussion> discussions) {
return discussions.stream()
.map(this::update)
.collect(Collectors.toList());
}
/**
* Уведомляет пользователя, если его никнейм упоминается в комментарии.
*/
private void notifyAboutPersonalAnswer(Discussion discussion, Note note) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(discussionLevel)) {
final Matcher matcher = PATTERN.matcher(note.getBody());
final Set<String> recipientsLogins = new HashSet<>();
private void notifyUpdateNote(Discussion oldDiscussion, Discussion discussion) {
final Map<Long, Note> oldNoteMap = oldDiscussion
.getNotes().stream()
.collect(Collectors.toMap(Note::getId, n -> n));
while (matcher.find()) {
final String login = matcher.group(0).replace("@", "");
recipientsLogins.add(login);
}
// Пользователь участвовал в обсуждении
final boolean userParticipatedInDiscussion = oldDiscussion.getNotes().stream()
.anyMatch(note -> personInformation.getId().equals(note.getAuthor().getId()));
if (recipientsLogins.contains(personInformation.getUsername())) {
final NewCommentPersonalNotify.NewCommentPersonalNotifyBuilder notifyBuilder = NewCommentPersonalNotify.builder()
.threadId(discussion.getId())
.mergeRequestName(discussion.getMergeRequest().getTitle())
.url(note.getWebUrl());
final Note threadFirstNote = discussion.getFirstNote();
if (TRUE.equals(discussion.getResolved())) {
notifyAboutCloseThread(threadFirstNote, oldNoteMap.get(threadFirstNote.getId()), discussion.getLastNote());
}
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final Optional<Note> prevLastNote = discussion.getPrevLastNote();
final Note firstNote = discussion.getFirstNote();
for (Note newNote : discussion.getNotes()) {
final Long newNoteId = newNote.getId();
if (!oldNoteMap.containsKey(newNoteId)) {
if (userParticipatedInDiscussion) {
notifyAboutNewAnswer(discussion, newNote);
} else {
notifyAboutPersonalAnswer(discussion, newNote);
if (!firstNote.equals(note)) {
notifyBuilder.message(note.getBody())
.authorName(note.getAuthor().getName());
}
if (prevLastNote.isPresent()) {
final Note prevNote = prevLastNote.get();
notifyBuilder.previousMessage(prevNote.getBody());
notifyBuilder.previousAuthor(prevNote.getAuthor().getName());
}
notifyBuilder
.discussionMessage(firstNote.getBody())
.discussionAuthor(firstNote.getAuthor().getName());
}
notifyService.send(notifyBuilder.build());
}
}
}
private void notifyAboutNewAnswer(Discussion discussion, Note note) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(discussionLevel)
&& !personInformation.getId().equals(note.getAuthor().getId())) {
final Note firstNote = discussion.getFirstNote();
final NewCommentPersonalNotify.NewCommentPersonalNotifyBuilder notifyBuilder = NewCommentPersonalNotify.builder()
.threadId(discussion.getId())
.url(note.getWebUrl())
.mergeRequestName(discussion.getMergeRequest().getTitle());
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final Optional<Note> prevLastNote = discussion.getPrevLastNote();
if (prevLastNote.isPresent()) {
final Note prevNote = prevLastNote.get();
notifyBuilder.previousMessage(prevNote.getBody());
notifyBuilder.previousAuthor(prevNote.getAuthor().getName());
}
notifyBuilder
.discussionMessage(firstNote.getBody())
.discussionAuthor(firstNote.getAuthor().getName())
.message(note.getBody())
.authorName(note.getAuthor().getName());
}
notifyService.send(notifyBuilder.build());
}
}
private void notifyAboutCloseThread(Note newNote, Note oldNote, Optional<Note> lastNote) {
@ -183,7 +218,7 @@ public class DiscussionServiceImpl implements DiscussionService {
if (isResolved(newNote, oldNote)) {
final MergeRequestForDiscussion mergeRequest = oldNote.getDiscussion().getMergeRequest();
final List<Discussion> discussions = getAllByMergeRequestId(mergeRequest.getId())
final List<Discussion> discussions = discussionService.getAllByMergeRequestId(mergeRequest.getId())
.stream()
.filter(discussion -> Objects.nonNull(discussion.getResponsible()))
.toList();
@ -194,7 +229,7 @@ public class DiscussionServiceImpl implements DiscussionService {
.filter(discussion -> personInformation.getId().equals(discussion.getFirstNote().getAuthor().getId()) && discussion.getResolved())
.count();
final ThreadCloseNotify.ThreadCloseNotifyBuilder notifyBuilder = ThreadCloseNotify.builder()
final ThreadClosePersonalNotify.ThreadClosePersonalNotifyBuilder notifyBuilder = ThreadClosePersonalNotify.builder()
.mergeRequestName(mergeRequest.getTitle())
.url(oldNote.getWebUrl())
.personTasks(allYouTasks)
@ -227,195 +262,4 @@ public class DiscussionServiceImpl implements DiscussionService {
&& !note.getResolvedBy().getId().equals(oldNote.getAuthor().getId()); // и решающий не является автором треда
}
@Override
public void answer(@NonNull String discussionId, @NonNull String text) {
final Discussion discussion = repository.findById(discussionId)
.orElseThrow(notFoundException("Дисскусия {0} не найдена", discussionId));
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest();
final Long projectId = mergeRequest.getProjectId();
final String requestUrl = MessageFormat.format(gitlabProperty.getNewNoteUrl(), projectId, mergeRequest.getTwoId(), discussion.getId(), text);
final RequestBody formBody = new FormBody.Builder().build();
final Request request = new Request.Builder()
.post(formBody)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.url(requestUrl)
.build();
try {
client.newCall(request).execute();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
@Override
public List<Discussion> getAllByMergeRequestId(@NonNull Long mergeRequestId) {
return repository.findAllByMergeRequestId(mergeRequestId);
}
@Override
public ExistContainer<Discussion, String> existsById(@NonNull Set<String> discussionIds) {
final List<Discussion> existsEntity = repository.findAllById(discussionIds);
final Set<String> existsIds = existsEntity.stream().map(Discussion::getId).collect(Collectors.toSet());
if (existsIds.containsAll(discussionIds)) {
return ExistContainer.allFind(existsEntity);
} else {
final Set<String> noExistsId = discussionIds.stream()
.filter(id -> !existsIds.contains(id))
.collect(Collectors.toSet());
return ExistContainer.notAllFind(existsEntity, noExistsId);
}
}
@Override
public List<Discussion> createAll(@NonNull List<Discussion> newDiscussions) {
return newDiscussions.stream()
.map(this::create)
.toList();
}
@Override
public List<Discussion> getAll() {
return repository.findAll();
}
@Override
public Set<String> getAllIds() {
return repository.findAllIds();
}
@Override
@Transactional
public void deleteById(@NonNull String discussionId) {
repository.deleteById(discussionId);
}
@Override
@Transactional
public void cleanOld() {
log.debug("Старт очистки старых дискуссий");
repository.cleanOld();
log.debug("Конец очистки старых дискуссий");
}
@Override
@Transactional
public void notification(boolean enable, String discussionId) {
repository.notification(enable, discussionId);
}
private void notifyAboutNewAnswer(Discussion discussion, Note note) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(discussionLevel)
&& !personInformation.getId().equals(note.getAuthor().getId())) {
final Note firstNote = discussion.getFirstNote();
final NewCommentNotify.NewCommentNotifyBuilder notifyBuilder = NewCommentNotify.builder()
.threadId(discussion.getId())
.url(note.getWebUrl())
.mergeRequestName(discussion.getMergeRequest().getTitle());
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final Optional<Note> prevLastNote = discussion.getPrevLastNote();
if (prevLastNote.isPresent()) {
final Note prevNote = prevLastNote.get();
notifyBuilder.previousMessage(prevNote.getBody());
notifyBuilder.previousAuthor(prevNote.getAuthor().getName());
}
notifyBuilder
.discussionMessage(firstNote.getBody())
.discussionAuthor(firstNote.getAuthor().getName())
.message(note.getBody())
.authorName(note.getAuthor().getName());
}
notifyService.send(notifyBuilder.build());
}
}
/**
* Уведомляет пользователя, если его никнейм упоминается в комментарии.
*/
private void notifyAboutPersonalAnswer(Discussion discussion, Note note) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(discussionLevel)) {
final Matcher matcher = PATTERN.matcher(note.getBody());
final Set<String> recipientsLogins = new HashSet<>();
while (matcher.find()) {
final String login = matcher.group(0).replace("@", "");
recipientsLogins.add(login);
}
if (recipientsLogins.contains(personInformation.getUsername())) {
final NewCommentNotify.NewCommentNotifyBuilder notifyBuilder = NewCommentNotify.builder()
.threadId(discussion.getId())
.mergeRequestName(discussion.getMergeRequest().getTitle())
.url(note.getWebUrl());
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final Optional<Note> prevLastNote = discussion.getPrevLastNote();
final Note firstNote = discussion.getFirstNote();
if (!firstNote.equals(note)) {
notifyBuilder.message(note.getBody())
.authorName(note.getAuthor().getName());
}
if (prevLastNote.isPresent()) {
final Note prevNote = prevLastNote.get();
notifyBuilder.previousMessage(prevNote.getBody());
notifyBuilder.previousAuthor(prevNote.getAuthor().getName());
}
notifyBuilder
.discussionMessage(firstNote.getBody())
.discussionAuthor(firstNote.getAuthor().getName());
}
notifyService.send(notifyBuilder.build());
}
}
}
/**
* <p>Уведомляет пользователя, если появился новый комментарий</p>
*/
private void notifyNewThread(Discussion discussion) {
final DiscussionLevel discussionLevel = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(discussionLevel)) {
final Note firstNote = discussion.getFirstNote();
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest();
final DiscussionNewNotify.DiscussionNewNotifyBuilder messageBuilder = DiscussionNewNotify.builder()
.url(firstNote.getWebUrl())
.threadId(discussion.getId())
.mergeRequestName(mergeRequest.getTitle())
.authorName(firstNote.getAuthor().getName());
if (NOTIFY_WITH_CONTEXT.equals(discussionLevel)) {
final List<Note> notes = discussion.getNotes();
messageBuilder
.discussionMessage(firstNote.getBody());
if (notes.size() > 1) {
for (int i = 1; i < notes.size(); i++) {
final Note note = notes.get(i);
messageBuilder.note(
new Pair<>(note.getAuthor().getName(), note.getBody())
);
}
}
}
notifyService.send(messageBuilder.build());
}
}
}

View File

@ -0,0 +1,388 @@
package dev.struchkov.bot.gitlab.core.handler;
import dev.struchkov.bot.gitlab.context.domain.MergeRequestState;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.changed.ApprovalChanged;
import dev.struchkov.bot.gitlab.context.domain.changed.AssigneeChanged;
import dev.struchkov.bot.gitlab.context.domain.changed.ReviewerChanged;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import dev.struchkov.bot.gitlab.context.domain.event.NewMergeRequestEvent;
import dev.struchkov.bot.gitlab.context.domain.event.UpdateMergeRequestEvent;
import dev.struchkov.bot.gitlab.context.domain.notify.group.mr.ConflictMrGroupNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.group.mr.NewMergeRequestGroupNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.group.mr.NoReviewerGroupNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ApprovalChangedMrPersonalNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictMrPersonalNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictResolveMrPersonalNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrForAssignee;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrForReview;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.StatusMrPersonalNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.UpdateMrPersonalNotify;
import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.bot.gitlab.context.service.NotifyService;
import dev.struchkov.bot.gitlab.context.service.PersonService;
import dev.struchkov.bot.gitlab.context.service.ProjectService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
import static java.lang.Boolean.TRUE;
@Component
@RequiredArgsConstructor
public class MergeRequestHandler {
private final NotifyService notifyService;
private final PersonService personService;
private final PersonInformation personInformation;
private final ProjectService projectService;
private final DiscussionService discussionService;
@EventListener
public void newMrHandle(NewMergeRequestEvent event) {
final MergeRequest mergeRequest = event.getMergeRequest();
final String projectName = projectService.getByIdOrThrow(mergeRequest.getProjectId()).getName();
final boolean userReviewer = mergeRequest.isUserReviewer();
final boolean userAssignee = mergeRequest.isUserAssignee();
if (mergeRequest.isConflict()) {
sendGroupNotifyAboutConflict(mergeRequest, projectName);
} else {
if (mergeRequest.isNotification()) {
if (userReviewer || userAssignee) {
if (userReviewer) sendNotifyNewMrReview(mergeRequest, projectName);
if (userAssignee) sendNotifyNewAssignee(mergeRequest, projectName, null);
}
}
sendGroupNotifyForReviewers(mergeRequest, projectName);
}
}
@EventListener
public void updateMrHandle(UpdateMergeRequestEvent event) {
final MergeRequest oldMergeRequest = event.getOldMergeRequest();
final MergeRequest newMergeRequest = event.getNewMergeRequest();
final AssigneeChanged assigneeChanged = event.getAssigneeChanged();
final ReviewerChanged reviewerChanged = event.getReviewerChanged();
final Optional<ApprovalChanged> optApprovalChanged = event.getOptApprovalChanged();
final boolean resolveConflict = oldMergeRequest.isConflict() && !newMergeRequest.isConflict();
final boolean newConflict = !oldMergeRequest.isConflict() && newMergeRequest.isConflict();
final boolean isChangedMr = !oldMergeRequest.getUpdatedDate().equals(newMergeRequest.getUpdatedDate());
final Project project = projectService.getByIdOrThrow(newMergeRequest.getProjectId());
if (oldMergeRequest.isNotification()) {
if (newConflict) {
personalNotifyAboutNewConflict(oldMergeRequest, newMergeRequest, project);
}
if (resolveConflict) {
personalNotifyAboutResolveConflict(oldMergeRequest, newMergeRequest, project);
}
if (isChangedMr) {
personalNotifyAboutStatus(oldMergeRequest, newMergeRequest, project);
personalNotifyAboutUpdate(oldMergeRequest, newMergeRequest, project);
}
if (reviewerChanged.isChanged()) {
notifyReviewer(reviewerChanged, newMergeRequest, project);
}
if (assigneeChanged.isChanged()) {
notifyAssignee(assigneeChanged, oldMergeRequest, newMergeRequest, project);
}
if (optApprovalChanged.isPresent()) {
final ApprovalChanged approvalChanged = optApprovalChanged.get();
notifyApproval(newMergeRequest, project, approvalChanged);
}
}
//групповые уведомления
if (newConflict) {
sendGroupNotifyAboutConflict(newMergeRequest, project.getName());
}
if (reviewerChanged.isChanged()) {
sendGroupNotifyForReviewers(newMergeRequest, project.getName());
}
}
private void sendGroupNotifyAboutConflict(MergeRequest mergeRequest, String projectName) {
final Optional<String> optAuthorUserName = personService.getTelegramUsernamesByPersonIds(mergeRequest.getAuthor().getId());
if (optAuthorUserName.isPresent()) {
final String authorUserName = optAuthorUserName.get();
notifyService.send(
ConflictMrGroupNotify.builder()
.projectName(projectName)
.url(mergeRequest.getWebUrl())
.title(mergeRequest.getTitle())
.milestone(mergeRequest.getMilestone())
.sourceBranch(mergeRequest.getSourceBranch())
.authorTelegramUsername(authorUserName)
.build()
);
}
}
private void sendGroupNotifyAboutNoReviewers(MergeRequest mergeRequest, String projectName) {
final Optional<String> optAuthorUserName = personService.getTelegramUsernamesByPersonIds(mergeRequest.getAuthor().getId());
if (optAuthorUserName.isPresent()) {
final String authorUsername = optAuthorUserName.get();
notifyService.send(
NoReviewerGroupNotify.builder()
.ownerTelegramUsername(authorUsername)
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.milestone(mergeRequest.getMilestone())
.projectName(projectName)
.targetBranch(mergeRequest.getTargetBranch())
.sourceBranch(mergeRequest.getSourceBranch())
.build()
);
}
}
private void sendGroupNotifyForReviewers(MergeRequest mergeRequest, String projectName) {
if (checkNotEmpty(mergeRequest.getReviewers())) {
final Map<Long, String> reviewerTelegramUsernames = personService.getTelegramUsernamesByPersonIds(mergeRequest.getReviewers().stream().map(Person::getId).collect(Collectors.toSet()));
if (!reviewerTelegramUsernames.isEmpty()) {
notifyService.send(
NewMergeRequestGroupNotify.builder()
.mrId(mergeRequest.getId())
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.author(mergeRequest.getAuthor().getName())
.reviewerTelegramUsernames(reviewerTelegramUsernames)
.milestone(mergeRequest.getMilestone())
.projectName(projectName)
.targetBranch(mergeRequest.getTargetBranch())
.sourceBranch(mergeRequest.getSourceBranch())
.description(mergeRequest.getDescription())
.build()
);
}
} else {
sendGroupNotifyAboutNoReviewers(mergeRequest, projectName);
}
}
private void notifyApproval(MergeRequest mergeRequest, Project project, ApprovalChanged approvalChanged) {
final Set<Person> newApproval = approvalChanged.getNewApproval().stream()
.filter(approval -> !personInformation.getId().equals(approval.getId()))
.collect(Collectors.toSet());
final Set<Person> dontApproval = approvalChanged.getDontApprove().stream()
.filter(approval -> !personInformation.getId().equals(approval.getId()))
.collect(Collectors.toSet());
if (checkNotEmpty(newApproval) || checkNotEmpty(dontApproval)) {
notifyService.send(
ApprovalChangedMrPersonalNotify.builder()
.mrId(mergeRequest.getId())
.milestone(mergeRequest.getMilestone())
.projectName(project.getName())
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.newApproval(newApproval)
.dontApproval(dontApproval)
.build()
);
}
}
private void sendNotifyNewMrReview(MergeRequest mergeRequest, String projectName) {
final NewMrForReview.NewMrForReviewBuilder builder = NewMrForReview.builder()
.mrId(mergeRequest.getId())
.projectName(projectName)
.labels(mergeRequest.getLabels())
.author(mergeRequest.getAuthor().getName())
.milestone(mergeRequest.getMilestone())
.description(mergeRequest.getDescription())
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.targetBranch(mergeRequest.getTargetBranch())
.sourceBranch(mergeRequest.getSourceBranch());
getAssignee(mergeRequest)
.map(Person::getName)
.ifPresent(builder::assignee);
notifyService.send(builder.build());
}
private void sendNotifyNewAssignee(MergeRequest mergeRequest, String projectName, String oldAssigneeName) {
final NewMrForAssignee.NewMrForAssigneeBuilder builder = NewMrForAssignee.builder()
.mrId(mergeRequest.getId())
.projectName(projectName)
.labels(mergeRequest.getLabels())
.author(mergeRequest.getAuthor().getName())
.description(mergeRequest.getDescription())
.milestone(mergeRequest.getMilestone())
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.targetBranch(mergeRequest.getTargetBranch())
.sourceBranch(mergeRequest.getSourceBranch())
.milestone(mergeRequest.getMilestone())
.reviewers(mergeRequest.getReviewers().stream().map(Person::getName).toList());
if (checkNotNull(oldAssigneeName)) {
builder.oldAssigneeName(oldAssigneeName);
getAssignee(mergeRequest)
.map(Person::getName)
.ifPresent(builder::newAssigneeName);
}
notifyService.send(builder.build());
}
private void personalNotifyAboutUpdate(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long botUserGitlabId = personInformation.getId();
if (
!botUserGitlabId.equals(mergeRequest.getAuthor().getId()) // Автор MR не пользователь приложения
&& !Objects.equals(oldMergeRequest.getDateLastCommit(), mergeRequest.getDateLastCommit())
&& !mergeRequest.isConflict() // MR не находится в состоянии конфликта
&& !botUserGitlabId.equals(oldMergeRequest.getAuthor().getId()) // и MR создан НЕ пользователем бота
) {
long allTask = 0;
long resolvedTask = 0;
long allYouTasks = 0;
long resolvedYouTask = 0;
final List<Discussion> discussions = discussionService.getAllByMergeRequestId(oldMergeRequest.getId());
for (Discussion discussion : discussions) {
if (checkNotNull(discussion.getResponsible())) {
final boolean isBotUserAuthorDiscussion = botUserGitlabId.equals(discussion.getFirstNote().getAuthor().getId());
allTask += 1;
if (isBotUserAuthorDiscussion) {
allYouTasks += 1;
}
if (TRUE.equals(discussion.getResolved())) {
resolvedTask += 1;
if (isBotUserAuthorDiscussion) {
resolvedYouTask += 1;
}
}
}
}
final UpdateMrPersonalNotify.UpdateMrPersonalNotifyBuilder notifyBuilder = UpdateMrPersonalNotify.builder()
.mrId(oldMergeRequest.getId())
.author(oldMergeRequest.getAuthor().getName())
.name(oldMergeRequest.getTitle())
.projectName(project.getName())
.url(oldMergeRequest.getWebUrl())
.allTasks(allTask)
.milestone(mergeRequest.getMilestone())
.allResolvedTasks(resolvedTask)
.personTasks(allYouTasks)
.personResolvedTasks(resolvedYouTask);
if (oldMergeRequest.isConflict() && !mergeRequest.isConflict()) {
notifyBuilder.comment("The conflict has been resolved");
}
notifyService.send(notifyBuilder.build());
}
}
protected void personalNotifyAboutNewConflict(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long gitlabUserId = personInformation.getId();
if (
!oldMergeRequest.isConflict() // У старого MR не было конфликта
&& mergeRequest.isConflict() // А у нового есть
&& gitlabUserId.equals(oldMergeRequest.getAuthor().getId()) // и MR создан пользователем бота
) {
notifyService.send(
ConflictMrPersonalNotify.builder()
.mrId(oldMergeRequest.getId())
.sourceBranch(oldMergeRequest.getSourceBranch())
.name(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.projectKey(project.getName())
.milestone(mergeRequest.getMilestone())
.build()
);
}
}
private void personalNotifyAboutResolveConflict(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long gitlabUserId = personInformation.getId();
if (oldMergeRequest.isConflict() && !mergeRequest.isConflict()) {
// проверяем даты коммитов, так как при пуше в target ветку MR у которого есть конфликт, конфликт на время пропадает. Судя по всему GitLab после пуша заново проверяет вероятность конфликта. Чаще всего конфликт никуда не девается.
if (Objects.equals(oldMergeRequest.getDateLastCommit(), mergeRequest.getDateLastCommit())) {
mergeRequest.setConflict(true);
} else {
if (gitlabUserId.equals(oldMergeRequest.getAuthor().getId())) {
notifyService.send(
ConflictResolveMrPersonalNotify.builder()
.mrId(oldMergeRequest.getId())
.sourceBranch(oldMergeRequest.getSourceBranch())
.name(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.projectKey(project.getName())
.milestone(mergeRequest.getMilestone())
.build()
);
}
}
}
}
protected void personalNotifyAboutStatus(MergeRequest oldMergeRequest, MergeRequest newMergeRequest, Project project) {
final MergeRequestState oldStatus = oldMergeRequest.getState();
final MergeRequestState newStatus = newMergeRequest.getState();
final Long gitlabUserId = personInformation.getId();
if (
!oldStatus.equals(newStatus) // статус изменился
&& gitlabUserId.equals(oldMergeRequest.getAuthor().getId()) // создатель MR является пользователем бота
) {
notifyService.send(
StatusMrPersonalNotify.builder()
.mrId(oldMergeRequest.getId())
.name(newMergeRequest.getTitle())
.url(oldMergeRequest.getWebUrl())
.projectName(project.getName())
.newStatus(newStatus)
.oldStatus(oldStatus)
.milestone(newMergeRequest.getMilestone())
.build()
);
}
}
private Optional<Person> getAssignee(MergeRequest mergeRequest) {
return Optional.ofNullable(mergeRequest.getAssignee());
}
//TODO [05.12.2022|uPagge]: Добавить уведомление, если происходит удаление
private void notifyAssignee(AssigneeChanged assigneeChanged, MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
switch (assigneeChanged) {
case BECOME -> sendNotifyNewAssignee(
mergeRequest, project.getName(), getAssignee(oldMergeRequest).map(Person::getName).orElse(null)
);
}
}
//TODO [05.12.2022|uPagge]: Добавить уведомление, если происходит удаление ревьювера
private void notifyReviewer(ReviewerChanged reviewerChanged, MergeRequest mergeRequest, Project project) {
switch (reviewerChanged) {
case BECOME -> sendNotifyNewMrReview(mergeRequest, project.getName());
}
}
}

View File

@ -0,0 +1,138 @@
package dev.struchkov.bot.gitlab.core.handler;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import dev.struchkov.bot.gitlab.context.domain.event.NewPipelineEvent;
import dev.struchkov.bot.gitlab.context.domain.event.UpdatePipelineEvent;
import dev.struchkov.bot.gitlab.context.domain.notify.group.pipeline.PipelineGroupNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.pipeline.PipelinePersonalNotify;
import dev.struchkov.bot.gitlab.context.service.NotifyService;
import dev.struchkov.bot.gitlab.context.service.PersonService;
import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.haiti.utils.Strings;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.CANCELED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.FAILED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.SKIPPED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.SUCCESS;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
@Component
@RequiredArgsConstructor
public class PipelineHandler {
// Статусы пайплайнов, о которых нужно уведомить
private static final Set<PipelineStatus> notificationStatus = Set.of(FAILED, SUCCESS, CANCELED, SKIPPED);
private final PersonService personService;
private final PersonInformation personInformation;
private final NotifyService notifyService;
private final ProjectService projectService;
@EventListener
public void newPipelineHandle(NewPipelineEvent event) {
final Pipeline pipeline = event.getPipeline();
final Optional<String> optProjectName = projectService.getProjectNameById(pipeline.getProjectId())
.map(Strings::escapeMarkdown);
if (isNeedPersonalNotifyPipeline(pipeline)) {
final PipelinePersonalNotify.PipelinePersonalNotifyBuilder builder = PipelinePersonalNotify.builder()
.newStatus(pipeline.getStatus())
.refName(pipeline.getRef())
.webUrl(pipeline.getWebUrl())
.oldStatus(PipelineStatus.NEW);
optProjectName.ifPresent(builder::projectName);
notifyService.send(builder.build());
}
if (isNeedPersonalGroupPipeline(pipeline)) {
final Optional<String> optTelegramUsername = personService.getTelegramUsernamesByPersonIds(pipeline.getPerson().getId());
if (optTelegramUsername.isPresent()) {
final String telegramUsername = optTelegramUsername.get();
final PipelineGroupNotify.PipelineGroupNotifyBuilder builder = PipelineGroupNotify.builder()
.newStatus(pipeline.getStatus())
.refName(pipeline.getRef())
.webUrl(pipeline.getWebUrl())
.oldStatus(PipelineStatus.NEW)
.ownerTelegramUsername(telegramUsername);
optProjectName.ifPresent(builder::projectName);
notifyService.send(builder.build());
}
}
}
@EventListener
public void updatePipelineHandle(UpdatePipelineEvent event) {
final Pipeline oldPipeline = event.getOldPipeline();
final Pipeline newPipeline = event.getNewPipeline();
if (!Objects.equals(oldPipeline.getUpdated(), newPipeline.getUpdated())) {
final Optional<String> optProjectName = projectService.getProjectNameById(newPipeline.getProjectId())
.map(Strings::escapeMarkdown);
if (isNeedPersonalNotifyPipeline(newPipeline)) {
final PipelinePersonalNotify.PipelinePersonalNotifyBuilder builder = PipelinePersonalNotify.builder()
.newStatus(newPipeline.getStatus())
.refName(newPipeline.getRef())
.webUrl(newPipeline.getWebUrl())
.oldStatus(oldPipeline.getStatus());
optProjectName.ifPresent(builder::projectName);
notifyService.send(builder.build());
}
if (isNeedPersonalGroupPipeline(newPipeline)) {
final Optional<String> optTelegramUsername = personService.getTelegramUsernamesByPersonIds(newPipeline.getPerson().getId());
if (optTelegramUsername.isEmpty()) {
final String telegramUsername = optTelegramUsername.get();
final PipelineGroupNotify.PipelineGroupNotifyBuilder builder = PipelineGroupNotify.builder()
.newStatus(newPipeline.getStatus())
.refName(newPipeline.getRef())
.webUrl(newPipeline.getWebUrl())
.oldStatus(oldPipeline.getStatus())
.ownerTelegramUsername(telegramUsername);
optProjectName.ifPresent(builder::projectName);
notifyService.send(builder.build());
}
}
}
}
private boolean isNeedPersonalNotifyPipeline(@NonNull Pipeline pipeline) {
final Person personPipelineCreator = pipeline.getPerson();
return notificationStatus.contains(pipeline.getStatus()) // Пайплайн имеет статус необходимый для уведомления
&& checkNotNull(personPipelineCreator) // Создатель пайплайна не null
&& personInformation.getId().equals(personPipelineCreator.getId()) // Пользователь приложения является инициатором пайплайна
&& LocalDateTime.now().minusDays(1).isBefore(pipeline.getCreated()); // Пайплан был создан не более 24 часов назад
}
private boolean isNeedPersonalGroupPipeline(@NonNull Pipeline pipeline) {
final Person personPipelineCreator = pipeline.getPerson();
return notificationStatus.contains(pipeline.getStatus()) // Пайплайн имеет статус необходимый для уведомления
&& checkNotNull(personPipelineCreator) // Создатель пайплайна не null
&& LocalDateTime.now().minusDays(1).isBefore(pipeline.getCreated()); // Пайплан был создан не более 24 часов назад
}
}

View File

@ -0,0 +1,36 @@
package dev.struchkov.bot.gitlab.core.handler;
import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import dev.struchkov.bot.gitlab.context.domain.event.NewProjectEvent;
import dev.struchkov.bot.gitlab.context.domain.notify.project.NewProjectPersonalNotify;
import dev.struchkov.bot.gitlab.context.service.NotifyService;
import dev.struchkov.bot.gitlab.context.service.PersonService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class ProjectHandler {
private final NotifyService notifyService;
private final PersonService personService;
@EventListener
public void handleNewProjectEvent(NewProjectEvent event) {
final Project newProject = event.getProject();
final String authorName = personService.getByIdOrThrown(newProject.getCreatorId()).getName();
notifyService.send(
NewProjectPersonalNotify.builder()
.projectId(newProject.getId())
.projectDescription(newProject.getDescription())
.projectName(newProject.getName())
.projectUrl(newProject.getWebUrl())
.sshUrlToRepo(newProject.getSshUrlToRepo())
.httpUrlToRepo(newProject.getHttpUrlToRepo())
.authorName(authorName)
.build()
);
}
}

View File

@ -1,4 +1,4 @@
package dev.struchkov.bot.gitlab.core.service.parser;
package dev.struchkov.bot.gitlab.core.parser;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
@ -7,12 +7,11 @@ import dev.struchkov.bot.gitlab.context.domain.entity.Note;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import dev.struchkov.bot.gitlab.sdk.domain.DiscussionJson;
import dev.struchkov.sdk.gitlab.core.GitlabSdkManager;
import dev.struchkov.sdk.gitlab.domain.GitlabUrl;
import dev.struchkov.sdk.gitlab.schema.note.DiscussionJson;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Component;
@ -21,15 +20,11 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
import static dev.struchkov.bot.gitlab.core.utils.StringUtils.H_PRIVATE_TOKEN;
import static dev.struchkov.haiti.utils.Checker.checkFalse;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
@ -41,35 +36,20 @@ import static dev.struchkov.haiti.utils.Checker.checkNotNull;
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class DiscussionParser {
public static final int PAGE_COUNT = 100;
//TODO [23.08.2024|uPagge]: Убрать
private final GitlabUrl gitlabUrl;
private final GitlabSdkManager gitlabSdkManager;
private final DiscussionService discussionService;
private final MergeRequestsService mergeRequestsService;
private final ConversionService conversionService;
private final ForkJoinPool forkJoinPool;
private final GitlabProperty gitlabProperty;
private final PersonProperty personProperty;
public DiscussionParser(
DiscussionService discussionService,
MergeRequestsService mergeRequestsService,
ConversionService conversionService,
@Qualifier("parserPool") ForkJoinPool forkJoinPool,
GitlabProperty gitlabProperty,
PersonProperty personProperty
) {
this.discussionService = discussionService;
this.mergeRequestsService = mergeRequestsService;
this.conversionService = conversionService;
this.forkJoinPool = forkJoinPool;
this.gitlabProperty = gitlabProperty;
this.personProperty = personProperty;
}
/**
* Поиск новых обсуждений
@ -85,11 +65,11 @@ public class DiscussionParser {
private void processingNewDiscussion(MergeRequestForDiscussion mergeRequest) {
int page = 1;
final List<DiscussionJson> discussionJson = getDiscussionJson(mergeRequest, page);
final List<DiscussionJson> discussionJson = gitlabSdkManager.getDiscussionForMergeRequest(mergeRequest.getProjectId(), mergeRequest.getTwoId(), page, PAGE_COUNT);
if (checkNotEmpty(discussionJson)) {
while (discussionJson.size() == PAGE_COUNT) {
discussionJson.addAll(getDiscussionJson(mergeRequest, ++page));
discussionJson.addAll(gitlabSdkManager.getDiscussionForMergeRequest(mergeRequest.getProjectId(), mergeRequest.getTwoId(), ++page, PAGE_COUNT));
}
createNewDiscussion(discussionJson, mergeRequest);
}
@ -170,11 +150,12 @@ public class DiscussionParser {
final List<Discussion> newDiscussions = new ArrayList<>();
for (Discussion discussion : discussions) {
if (checkNotNull(discussion.getMergeRequest())) {
getOldDiscussionJson(discussion)
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest();
if (checkNotNull(mergeRequest)) {
gitlabSdkManager.getDiscussionById(mergeRequest.getProjectId(), mergeRequest.getTwoId(), discussion.getId())
.map(json -> {
final Discussion newDiscussion = conversionService.convert(json, Discussion.class);
newDiscussion.getNotes().forEach(createNoteLink(discussion.getMergeRequest()));
newDiscussion.getNotes().forEach(createNoteLink(mergeRequest));
return newDiscussion;
}).ifPresent(newDiscussions::add);
} else {
@ -190,33 +171,10 @@ public class DiscussionParser {
log.debug("Конец обработки старых дискуссий");
}
private Optional<DiscussionJson> getOldDiscussionJson(Discussion discussion) {
return HttpParse.request(createLinkOldDiscussion(discussion))
.header(ACCEPT)
.header(H_PRIVATE_TOKEN, personProperty.getToken())
.execute(DiscussionJson.class);
}
private String createLinkOldDiscussion(Discussion discussion) {
return MessageFormat.format(
gitlabProperty.getDiscussionUrl(),
discussion.getMergeRequest().getProjectId(),
discussion.getMergeRequest().getTwoId(),
discussion.getId()
);
}
private List<DiscussionJson> getDiscussionJson(MergeRequestForDiscussion mergeRequest, int page) {
return HttpParse.request(MessageFormat.format(gitlabProperty.getDiscussionsUrl(), mergeRequest.getProjectId(), mergeRequest.getTwoId(), page, PAGE_COUNT))
.header(ACCEPT)
.header(H_PRIVATE_TOKEN, personProperty.getToken())
.executeList(DiscussionJson.class);
}
private Consumer<Note> createNoteLink(MergeRequestForDiscussion mergeRequest) {
return note -> {
final String url = MessageFormat.format(
gitlabProperty.getNoteUrl(),
gitlabUrl.getNote(),
mergeRequest.getWebUrl(),
note.getId()
);

View File

@ -1,85 +1,61 @@
package dev.struchkov.bot.gitlab.core.service.parser;
package dev.struchkov.bot.gitlab.core.parser;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.IdAndStatusPr;
import dev.struchkov.bot.gitlab.context.domain.MergeRequestState;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
import dev.struchkov.bot.gitlab.core.service.parser.forktask.GetAllMergeRequestForProjectTask;
import dev.struchkov.bot.gitlab.core.service.parser.forktask.GetSingleMergeRequestTask;
import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import dev.struchkov.bot.gitlab.core.utils.StringUtils;
import dev.struchkov.bot.gitlab.sdk.domain.CommitJson;
import dev.struchkov.bot.gitlab.sdk.domain.MergeRequestJson;
import dev.struchkov.haiti.utils.container.Pair;
import dev.struchkov.sdk.gitlab.core.GitlabSdkManager;
import dev.struchkov.sdk.gitlab.schema.approval.ApprovalJson;
import dev.struchkov.sdk.gitlab.schema.mergerequest.MergeRequestJson;
import dev.struchkov.sdk.gitlab.schema.repository.CommitJson;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Service;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
import static dev.struchkov.haiti.utils.concurrent.ForkJoinUtils.pullTaskResult;
import static dev.struchkov.haiti.utils.concurrent.ForkJoinUtils.pullTaskResults;
import static java.util.stream.Collectors.toList;
@Slf4j
@Service
@RequiredArgsConstructor
public class MergeRequestParser {
private static final Set<MergeRequestState> OLD_STATUSES = Set.of(
MergeRequestState.MERGED, MergeRequestState.OPENED, MergeRequestState.CLOSED
);
private final GitlabProperty gitlabProperty;
private final GitlabSdkManager gitlabSdkManager;
private final MergeRequestsService mergeRequestsService;
private final ProjectService projectService;
private final ConversionService conversionService;
private final PersonProperty personProperty;
private final ForkJoinPool forkJoinPool;
public MergeRequestParser(
GitlabProperty gitlabProperty,
MergeRequestsService mergeRequestsService,
ProjectService projectService,
ConversionService conversionService,
PersonProperty personProperty,
@Qualifier("parserPool") ForkJoinPool forkJoinPool
) {
this.gitlabProperty = gitlabProperty;
this.mergeRequestsService = mergeRequestsService;
this.projectService = projectService;
this.conversionService = conversionService;
this.personProperty = personProperty;
this.forkJoinPool = forkJoinPool;
}
public void parsingOldMergeRequest() {
log.debug("Старт обработки старых MR");
final Set<IdAndStatusPr> existIds = mergeRequestsService.getAllId(OLD_STATUSES);
final Set<Pair<Long, Long>> existIds = mergeRequestsService.getAllId(OLD_STATUSES).stream()
.map(idAndStatusPr -> new Pair<>(idAndStatusPr.getProjectId(), idAndStatusPr.getTwoId()))
.collect(Collectors.toSet());
final List<MergeRequest> newMergeRequests = getOldMergeRequests(existIds).stream()
final List<MergeRequest> newMergeRequests = gitlabSdkManager.getAllMergeRequestById(existIds).stream()
.map(mergeRequestJson -> {
final MergeRequest newMergeRequest = conversionService.convert(mergeRequestJson, MergeRequest.class);
parsingCommits(newMergeRequest);
parsingApprovals(newMergeRequest);
return newMergeRequest;
})
.collect(Collectors.toList());
.collect(toList());
if (checkNotEmpty(newMergeRequests)) {
personMapping(newMergeRequests);
@ -88,28 +64,11 @@ public class MergeRequestParser {
log.debug("Конец обработки старых MR");
}
private List<MergeRequestJson> getOldMergeRequests(Set<IdAndStatusPr> existIds) {
final List<ForkJoinTask<Optional<MergeRequestJson>>> tasks = existIds.stream()
.map(
existId -> new GetSingleMergeRequestTask(
gitlabProperty.getMergeRequestUrl(),
existId.getProjectId(),
existId.getTwoId(),
personProperty.getToken()
)
).map(forkJoinPool::submit)
.collect(Collectors.toList());
return pullTaskResult(tasks).stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
}
public void parsingNewMergeRequest() {
log.debug("Старт обработки новых MR");
final Set<Long> projectIds = projectService.getAllIdByProcessingEnable();
final List<MergeRequestJson> mergeRequestJsons = getMergeRequests(projectIds);
final List<MergeRequestJson> mergeRequestJsons = gitlabSdkManager.getAllMergeRequestByProjectIds(projectIds);
if (checkNotEmpty(mergeRequestJsons)) {
final Set<Long> jsonIds = mergeRequestJsons.stream()
@ -124,6 +83,7 @@ public class MergeRequestParser {
.map(json -> {
final MergeRequest mergeRequest = conversionService.convert(json, MergeRequest.class);
parsingCommits(mergeRequest);
parsingApprovals(mergeRequest);
return mergeRequest;
})
.toList();
@ -138,27 +98,13 @@ public class MergeRequestParser {
log.debug("Конец обработки новых MR");
}
/**
* Позволяет получить MR для переданных идентификаторов проектов.
*
* @param projectIds идентификаторы проектов
* @return полученные у GitLab MergeRequests
*/
private List<MergeRequestJson> getMergeRequests(Set<Long> projectIds) {
final List<ForkJoinTask<List<MergeRequestJson>>> tasks = projectIds.stream()
.map(projectId -> new GetAllMergeRequestForProjectTask(projectId, gitlabProperty.getOpenMergeRequestsUrl(), personProperty.getToken()))
.map(forkJoinPool::submit)
.collect(Collectors.toList());
return pullTaskResults(tasks);
}
private static void personMapping(List<MergeRequest> newMergeRequests) {
final Map<Long, Person> personMap = Stream.concat(
newMergeRequests.stream()
.flatMap(mergeRequest -> Stream.of(mergeRequest.getAssignee(), mergeRequest.getAuthor())),
newMergeRequests.stream()
.flatMap(mergeRequest -> mergeRequest.getReviewers().stream())
.flatMap(mergeRequest -> Stream.concat(mergeRequest.getReviewers().stream(), mergeRequest.getApprovals().stream()))
).distinct()
.filter(Objects::nonNull)
.collect(Collectors.toMap(Person::getId, p -> p));
@ -174,21 +120,35 @@ public class MergeRequestParser {
newMergeRequest.setReviewers(
newMergeRequest.getReviewers().stream()
.map(reviewer -> personMap.get(reviewer.getId()))
.collect(Collectors.toList())
.collect(toList())
);
newMergeRequest.setApprovals(
newMergeRequest.getApprovals().stream()
.map(approval -> personMap.get(approval.getId()))
.collect(toList())
);
}
}
//TODO [19.01.2024|uPagge]: Переделать в многопоточный режим
private void parsingCommits(MergeRequest mergeRequest) {
final List<CommitJson> commitJson = HttpParse.request(
MessageFormat.format(gitlabProperty.getLastCommitOfMergeRequestUrl(), mergeRequest.getProjectId(), mergeRequest.getTwoId())
)
.header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.executeList(CommitJson.class);
if (commitJson != null && !commitJson.isEmpty()) {
final List<CommitJson> commitJson = gitlabSdkManager.getAllCommitByProjectId(mergeRequest.getProjectId(), mergeRequest.getTwoId());
if (checkNotEmpty(commitJson)) {
mergeRequest.setDateLastCommit(commitJson.get(0).getCreatedDate());
}
}
//TODO [19.01.2024|uPagge]: Переделать в многопоточный режим
private void parsingApprovals(MergeRequest mergeRequest) {
final List<Person> personApprovals = gitlabSdkManager.getAllApprovalForMergeRequest(mergeRequest.getProjectId(), mergeRequest.getTwoId()).stream()
.map(ApprovalJson::getUser)
.map(personJson -> conversionService.convert(personJson, Person.class))
.collect(toList());
if (checkNotEmpty(personApprovals)) {
mergeRequest.setApprovals(personApprovals);
}
}
}

View File

@ -0,0 +1,111 @@
package dev.struchkov.bot.gitlab.core.parser;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import dev.struchkov.bot.gitlab.context.service.PipelineService;
import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.haiti.utils.container.Pair;
import dev.struchkov.sdk.gitlab.core.GitlabSdkManager;
import dev.struchkov.sdk.gitlab.schema.pipeline.PipelineShortJson;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.CREATED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.MANUAL;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.PENDING;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.PREPARING;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.RUNNING;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.WAITING_FOR_RESOURCE;
import static dev.struchkov.haiti.utils.Checker.checkFalse;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
/**
* Парсер пайплайнов.
*
* @author upagge 17.01.2021
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PipelineParser {
private static final Set<PipelineStatus> oldSOLD_STATUSES = Set.of(
CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING, RUNNING, MANUAL
);
private final GitlabSdkManager gitlabSdkManager;
private final PipelineService pipelineService;
private final ProjectService projectService;
private final ConversionService conversionService;
private LocalDateTime lastUpdate = LocalDateTime.now();
public void scanNewPipeline() {
log.debug("Старт обработки новых пайплайнов");
final Set<Long> projectIds = projectService.getAllIdByProcessingEnable();
final Map<Long, Long> pipelineProjectMap = getPipelineShortJsons(projectIds).stream()
.collect(toMap(PipelineShortJson::getId, PipelineShortJson::getProjectId));
if (checkNotEmpty(pipelineProjectMap)) {
final ExistContainer<Pipeline, Long> existContainer = pipelineService.existsById(pipelineProjectMap.keySet());
if (checkFalse(existContainer.isAllFound())) {
final Set<Pair<Long, Long>> idsNotFound = existContainer.getIdNoFound().stream()
.map(pipelineId -> new Pair<>(pipelineProjectMap.get(pipelineId), pipelineId))
.collect(toSet());
final List<Pipeline> newPipelines = gitlabSdkManager.getAllPipelineForProject(idsNotFound).stream()
.map(json -> conversionService.convert(json, Pipeline.class))
.collect(Collectors.toList());
;
if (checkNotEmpty(newPipelines)) {
pipelineService.createAll(newPipelines);
}
}
}
log.debug("Конец обработки новых пайплайнов");
}
private List<PipelineShortJson> getPipelineShortJsons(Set<Long> projectIds) {
final LocalDateTime newLastUpdate = LocalDateTime.now();
final List<PipelineShortJson> pipelineJsons = gitlabSdkManager.getAllPipeline(projectIds, lastUpdate.minusHours(12L));
lastUpdate = newLastUpdate;
return pipelineJsons;
}
public void scanOldPipeline() {
log.debug("Старт обработки старых пайплайнов");
final List<Pipeline> pipelines = pipelineService.getAllByStatuses(oldSOLD_STATUSES);
final List<Pipeline> newPipelines = gitlabSdkManager.getAllPipelineForProject(
pipelines.stream()
.map(pipeline -> new Pair<>(pipeline.getProjectId(), pipeline.getId()))
.collect(toSet())
).stream()
.map(json -> conversionService.convert(json, Pipeline.class))
.collect(Collectors.toList());
if (checkNotEmpty(newPipelines)) {
pipelineService.updateAll(newPipelines);
}
log.debug("Конец обработки старых пайплайнов");
}
}

View File

@ -1,29 +1,23 @@
package dev.struchkov.bot.gitlab.core.service.parser;
package dev.struchkov.bot.gitlab.core.parser;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import dev.struchkov.bot.gitlab.context.service.PersonService;
import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import dev.struchkov.bot.gitlab.core.utils.StringUtils;
import dev.struchkov.bot.gitlab.sdk.domain.PersonJson;
import dev.struchkov.bot.gitlab.sdk.domain.ProjectJson;
import dev.struchkov.sdk.gitlab.core.GitlabSdkManager;
import dev.struchkov.sdk.gitlab.domain.GitlabProjectParam;
import dev.struchkov.sdk.gitlab.schema.repository.ProjectJson;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Service;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
import static dev.struchkov.haiti.context.exception.ConvertException.convertException;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@ -37,33 +31,28 @@ import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@RequiredArgsConstructor
public class ProjectParser {
public static final String OWNER = "&owned=true";
public static final String PRIVATE = "&visibility=private";
private final GitlabSdkManager gitlabSdkManager;
private final ConversionService conversionService;
private final MergeRequestsService mergeRequestsService;
private final ProjectService projectService;
private final PersonService personService;
private final ConversionService conversionService;
private final GitlabProperty gitlabProperty;
private final PersonProperty personProperty;
public void parseAllProjectOwner() {
log.debug("Старт обработки всех проектов, где пользователь владелец");
parseProjects(OWNER);
parseProjects(GitlabProjectParam.OWNER);
log.debug("Конец обработки всех проектов, где пользователь владелец");
}
public void parseAllPrivateProject() {
log.debug("Старт обработки приватных проектов");
parseProjects(PRIVATE);
parseProjects(GitlabProjectParam.PRIVATE);
log.debug("Конец обработки приватных проектов");
}
private void parseProjects(String param) {
private void parseProjects(GitlabProjectParam param) {
int page = 1;
List<ProjectJson> projectJsons = getProjectJsons(page, param);
List<ProjectJson> projectJsons = gitlabSdkManager.getAllProject(page, param);
while (checkNotEmpty(projectJsons)) {
@ -83,15 +72,12 @@ public class ProjectParser {
projectService.createAll(newProjects);
}
projectJsons = getProjectJsons(++page, param);
projectJsons = gitlabSdkManager.getAllProject(++page, param);
}
}
public Project parseByUrl(String projectUrl) {
final ProjectJson projectJson = HttpParse.request(projectUrl)
.header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.execute(ProjectJson.class)
final ProjectJson projectJson = gitlabSdkManager.getProjectByUrl(projectUrl)
.orElseThrow(convertException("Error adding a repository"));
if (!projectService.existsById(projectJson.getId())) {
createNewPersons(List.of(projectJson));
@ -114,10 +100,7 @@ public class ProjectParser {
final List<Person> newPersons = notFoundId.stream()
.map(
userId -> HttpParse.request(gitlabProperty.getUsersUrl() + "/" + userId)
.header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.execute(PersonJson.class)
userId -> gitlabSdkManager.getPersonById(userId)
.map(json -> conversionService.convert(json, Person.class))
.orElseThrow(convertException("Ошибка преобразования нового пользователя"))
).toList();
@ -126,13 +109,5 @@ public class ProjectParser {
}
}
private List<ProjectJson> getProjectJsons(int page, String... params) {
String param = String.join("", params);
final String url = MessageFormat.format(gitlabProperty.getProjectsUrl(), page);
return HttpParse.request(url + param)
.header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.executeList(ProjectJson.class);
}
}

View File

@ -1,4 +1,4 @@
package dev.struchkov.bot.gitlab.core.service.impl;
package dev.struchkov.bot.gitlab.core.service;
import dev.struchkov.bot.gitlab.context.domain.entity.AppSetting;
import dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel;

View File

@ -0,0 +1,174 @@
package dev.struchkov.bot.gitlab.core.service;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequestForDiscussion;
import dev.struchkov.bot.gitlab.context.domain.entity.Note;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel;
import dev.struchkov.bot.gitlab.context.repository.DiscussionRepository;
import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.sdk.gitlab.core.GitlabSdkManager;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.event.NewDiscussionEvent.newDiscussion;
import static dev.struchkov.bot.gitlab.context.domain.event.UpdateDiscussionEvent.updateDiscussion;
import static dev.struchkov.bot.gitlab.context.domain.notify.level.DiscussionLevel.WITHOUT_NOTIFY;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
/**
* Сервис для работы с дискуссиями.
*
* @author upagge 11.02.2021
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DiscussionServiceImpl implements DiscussionService {
private final DiscussionRepository repository;
private final AppSettingService settingService;
private final GitlabSdkManager gitlabSdkManager;
private final ApplicationEventPublisher eventPublisher;
@Override
@Transactional
public Discussion create(@NonNull Discussion discussion) {
final DiscussionLevel levelDiscussionNotify = settingService.getLevelDiscussionNotify();
if (!WITHOUT_NOTIFY.equals(levelDiscussionNotify)) {
discussion.setNotification(true);
eventPublisher.publishEvent(newDiscussion(discussion));
} else {
discussion.setNotification(false);
}
final boolean resolved = discussion.getNotes().stream()
.allMatch(note -> note.isResolvable() && note.getResolved());
discussion.setResolved(resolved);
return repository.save(discussion);
}
@Override
@Transactional
public Discussion update(@NonNull Discussion discussion) {
final Discussion oldDiscussion = repository.findById(discussion.getId())
.orElseThrow(notFoundException("Дискуссия не найдена"));
discussion.setResponsible(oldDiscussion.getResponsible());
discussion.setMergeRequest(oldDiscussion.getMergeRequest());
discussion.setNotification(oldDiscussion.isNotification());
final Person responsiblePerson = discussion.getResponsible();
if (checkNotNull(responsiblePerson)) {
for (Note note : discussion.getNotes()) {
if (responsiblePerson.getId().equals(note.getAuthor().getId())) {
note.setAuthor(responsiblePerson);
}
final Person resolvedBy = note.getResolvedBy();
if (checkNotNull(resolvedBy)) {
if (responsiblePerson.getId().equals(resolvedBy.getId())) {
note.setResolvedBy(responsiblePerson);
}
}
}
}
final boolean resolved = discussion.getNotes().stream()
.allMatch(note -> note.isResolvable() && note.getResolved());
discussion.setResolved(resolved);
eventPublisher.publishEvent(updateDiscussion(oldDiscussion, discussion));
return repository.save(discussion);
}
@Override
public List<Discussion> updateAll(@NonNull List<Discussion> discussions) {
return discussions.stream()
.map(this::update)
.collect(Collectors.toList());
}
@Override
public void answer(@NonNull String discussionId, @NonNull String text) {
final Discussion discussion = repository.findById(discussionId)
.orElseThrow(notFoundException("Дисскусия {0} не найдена", discussionId));
final MergeRequestForDiscussion mergeRequest = discussion.getMergeRequest();
final Long projectId = mergeRequest.getProjectId();
gitlabSdkManager.sendMessageToDiscussion(projectId, mergeRequest.getTwoId(), discussionId, text);
}
@Override
public List<Discussion> getAllByMergeRequestId(@NonNull Long mergeRequestId) {
return repository.findAllByMergeRequestId(mergeRequestId);
}
@Override
public ExistContainer<Discussion, String> existsById(@NonNull Set<String> discussionIds) {
final List<Discussion> existsEntity = repository.findAllById(discussionIds);
final Set<String> existsIds = existsEntity.stream().map(Discussion::getId).collect(Collectors.toSet());
if (existsIds.containsAll(discussionIds)) {
return ExistContainer.allFind(existsEntity);
} else {
final Set<String> noExistsId = discussionIds.stream()
.filter(id -> !existsIds.contains(id))
.collect(Collectors.toSet());
return ExistContainer.notAllFind(existsEntity, noExistsId);
}
}
@Override
public List<Discussion> createAll(@NonNull List<Discussion> newDiscussions) {
return newDiscussions.stream()
.map(this::create)
.toList();
}
@Override
public List<Discussion> getAll() {
return repository.findAll();
}
@Override
public Set<String> getAllIds() {
return repository.findAllIds();
}
@Override
@Transactional
public void deleteById(@NonNull String discussionId) {
repository.deleteById(discussionId);
}
@Override
@Transactional
public void cleanOld() {
log.debug("Старт очистки старых дискуссий");
repository.cleanOld();
log.debug("Конец очистки старых дискуссий");
}
@Override
@Transactional
public void notification(boolean enable, String discussionId) {
repository.notification(enable, discussionId);
}
}

View File

@ -0,0 +1,199 @@
package dev.struchkov.bot.gitlab.core.service;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.IdAndStatusPr;
import dev.struchkov.bot.gitlab.context.domain.MergeRequestState;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.changed.ApprovalChanged;
import dev.struchkov.bot.gitlab.context.domain.changed.AssigneeChanged;
import dev.struchkov.bot.gitlab.context.domain.changed.ReviewerChanged;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequestForDiscussion;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.repository.MergeRequestRepository;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.MergeRequestState.CLOSED;
import static dev.struchkov.bot.gitlab.context.domain.MergeRequestState.MERGED;
import static dev.struchkov.bot.gitlab.context.domain.event.NewMergeRequestEvent.newMergeRequest;
import static dev.struchkov.bot.gitlab.context.domain.event.UpdateMergeRequestEvent.updateMergeRequest;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@Slf4j
@Service
@RequiredArgsConstructor
public class MergeRequestsServiceImpl implements MergeRequestsService {
public static final Set<MergeRequestState> DELETE_STATES = Set.of(MERGED, CLOSED);
private final ApplicationEventPublisher eventPublisher;
private final MergeRequestRepository repository;
private final PersonInformation personInformation;
@Override
@Transactional
public MergeRequest create(@NonNull MergeRequest mergeRequest) {
final boolean botUserReviewer = isBotUserReviewer(mergeRequest);
final boolean botUserAssignee = isBotUserAssigneeAndNotAuthor(mergeRequest);
final boolean botUserAuthor = personInformation.getId().equals(mergeRequest.getAuthor().getId());
mergeRequest.setNotification(botUserReviewer || botUserAssignee || botUserAuthor);
mergeRequest.setUserAssignee(botUserAssignee);
mergeRequest.setUserReviewer(botUserReviewer);
final MergeRequest savedMergeRequest = repository.save(mergeRequest);
eventPublisher.publishEvent(newMergeRequest(savedMergeRequest));
return savedMergeRequest;
}
private boolean isBotUserAssigneeAndNotAuthor(MergeRequest mergeRequest) {
final Long gitlabUserId = personInformation.getId();
final Optional<Person> optAssignee = getAssignee(mergeRequest);
final Person author = mergeRequest.getAuthor();
if (optAssignee.isPresent()) {
final Person assignee = optAssignee.get();
if (gitlabUserId.equals(assignee.getId()) && !isAuthorSameAssignee(author, assignee)) {
return true;
}
}
return false;
}
/**
* Создатель MR является ответственным за этот MR
*
* @return true, если автор и ответственный один и тот же человек.
*/
private boolean isAuthorSameAssignee(Person author, Person assignee) {
return author.getId().equals(assignee.getId());
}
private boolean isBotUserReviewer(MergeRequest savedMergeRequest) {
final List<Person> reviewers = savedMergeRequest.getReviewers();
final Long botUserGitlabId = personInformation.getId();
if (checkNotEmpty(reviewers)) {
for (Person reviewer : reviewers) {
if (botUserGitlabId.equals(reviewer.getId())) {
return true;
}
}
}
return false;
}
@Override
@Transactional
public MergeRequest update(@NonNull MergeRequest mergeRequest) {
final MergeRequest oldMergeRequest = repository.findById(mergeRequest.getId())
.orElseThrow(notFoundException("MergeRequest не найден"));
mergeRequest.setNotification(oldMergeRequest.isNotification());
final Long gitlabUserId = personInformation.getId();
final AssigneeChanged assigneeChanged = AssigneeChanged.valueOf(gitlabUserId, getAssignee(oldMergeRequest), getAssignee(mergeRequest));
final ReviewerChanged reviewerChanged = ReviewerChanged.valueOf(gitlabUserId, oldMergeRequest.getReviewers(), mergeRequest.getReviewers());
final Optional<ApprovalChanged> optApprovalChanged = ApprovalChanged.approvalChanged(oldMergeRequest.getApprovals(), mergeRequest.getApprovals());
mergeRequest.setUserAssignee(assigneeChanged.getNewStatus(oldMergeRequest.isUserAssignee()));
mergeRequest.setUserReviewer(reviewerChanged.getNewStatus(oldMergeRequest.isUserReviewer()));
eventPublisher.publishEvent(
updateMergeRequest(oldMergeRequest, mergeRequest, assigneeChanged, reviewerChanged, optApprovalChanged)
);
return repository.save(mergeRequest);
}
@Override
@Transactional
public List<MergeRequest> updateAll(@NonNull List<MergeRequest> mergeRequests) {
return mergeRequests.stream()
.map(this::update)
.collect(Collectors.toList());
}
@Override
public Set<IdAndStatusPr> getAllId(Set<MergeRequestState> statuses) {
return repository.findAllIdByStateIn(statuses);
}
@Override
public List<MergeRequestForDiscussion> getAllForDiscussion() {
return repository.findAllForDiscussion();
}
@Override
public ExistContainer<MergeRequest, Long> existsById(@NonNull Set<Long> mergeRequestIds) {
final List<MergeRequest> existsEntity = repository.findAllById(mergeRequestIds);
final Set<Long> existsIds = existsEntity.stream().map(MergeRequest::getId).collect(Collectors.toSet());
if (existsIds.containsAll(mergeRequestIds)) {
return ExistContainer.allFind(existsEntity);
} else {
final Set<Long> noExistsId = mergeRequestIds.stream()
.filter(id -> !existsIds.contains(id))
.collect(Collectors.toSet());
return ExistContainer.notAllFind(existsEntity, noExistsId);
}
}
@Override
public List<MergeRequest> createAll(List<MergeRequest> newMergeRequests) {
return newMergeRequests.stream()
.map(this::create)
.toList();
}
@Override
public List<MergeRequest> getAllByReviewerId(@NonNull Long personId) {
return repository.findAllByReviewerId(personId);
}
@Override
public void cleanOld() {
log.debug("Старт очистки старых MR");
repository.deleteByStates(DELETE_STATES);
log.debug("Конец очистки старых MR");
}
@Override
@Transactional(readOnly = true)
public Set<Long> getAllIds() {
return repository.findAllIds();
}
@Override
@Transactional
public void notification(boolean enable, @NonNull Long mrId) {
repository.notification(enable, mrId);
}
@Override
@Transactional
public void notificationByProjectId(boolean enable, @NonNull Set<Long> projectIds) {
repository.notificationByProjectId(enable, projectIds);
}
private Optional<Person> getAssignee(MergeRequest mergeRequest) {
return Optional.ofNullable(mergeRequest.getAssignee());
}
}

View File

@ -1,4 +1,4 @@
package dev.struchkov.bot.gitlab.core.service.impl.note;
package dev.struchkov.bot.gitlab.core.service;
import dev.struchkov.bot.gitlab.context.domain.entity.Note;
import dev.struchkov.bot.gitlab.context.repository.NoteRepository;

View File

@ -1,6 +1,8 @@
package dev.struchkov.bot.gitlab.core.service.impl;
package dev.struchkov.bot.gitlab.core.service;
import dev.struchkov.bot.gitlab.context.domain.notify.Notify;
import dev.struchkov.bot.gitlab.context.domain.notify.GroupNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.PersonalNotify;
import dev.struchkov.bot.gitlab.context.prop.GroupNotifyProperty;
import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.bot.gitlab.context.service.MessageSendService;
import dev.struchkov.bot.gitlab.context.service.NotifyService;
@ -10,22 +12,33 @@ import org.springframework.stereotype.Service;
@Service
public class NotifyServiceImpl implements NotifyService {
private final GroupNotifyProperty groupNotifyProperty;
private final MessageSendService messageSendService;
private final AppSettingService settingService;
public NotifyServiceImpl(
@Lazy MessageSendService messageSendService,
GroupNotifyProperty groupNotifyProperty, @Lazy MessageSendService messageSendService,
AppSettingService settingService
) {
this.groupNotifyProperty = groupNotifyProperty;
this.messageSendService = messageSendService;
this.settingService = settingService;
}
@Override
public <T extends Notify> void send(T notify) {
public <T extends PersonalNotify> void send(T notify) {
if (settingService.isEnableAllNotify()) {
messageSendService.send(notify);
}
}
@Override
public <T extends GroupNotify> void send(T notify) {
if (settingService.isEnableAllNotify()) {
if (groupNotifyProperty.getChatId().isPresent()) {
messageSendService.send(notify);
}
}
}
}

View File

@ -1,7 +1,8 @@
package dev.struchkov.bot.gitlab.core.service.impl;
package dev.struchkov.bot.gitlab.core.service;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.PersonTelegram;
import dev.struchkov.bot.gitlab.context.repository.PersonRepository;
import dev.struchkov.bot.gitlab.context.service.PersonService;
import lombok.NonNull;
@ -10,6 +11,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -29,11 +32,6 @@ public class PersonServiceImpl implements PersonService {
return repository.save(person);
}
@Override
public Person update(@NonNull Person person) {
return repository.save(person);
}
@Override
@Transactional(readOnly = true)
public Person getByIdOrThrown(@NonNull Long personId) {
@ -64,4 +62,15 @@ public class PersonServiceImpl implements PersonService {
.toList();
}
@Override
public Optional<String> getTelegramUsernamesByPersonIds(Long personId) {
return repository.findTelegramInfoById(personId).map(PersonTelegram::getTelegramUserName);
}
@Override
public Map<Long, String> getTelegramUsernamesByPersonIds(Set<Long> personIds) {
return repository.findAllTelegramInfoByIds(personIds).stream()
.collect(Collectors.toMap(PersonTelegram::getId, PersonTelegram::getTelegramUserName));
}
}

View File

@ -1,17 +1,14 @@
package dev.struchkov.bot.gitlab.core.service.impl;
package dev.struchkov.bot.gitlab.core.service;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import dev.struchkov.bot.gitlab.context.domain.notify.pipeline.PipelineNotify;
import dev.struchkov.bot.gitlab.context.repository.PipelineRepository;
import dev.struchkov.bot.gitlab.context.service.NotifyService;
import dev.struchkov.bot.gitlab.context.service.PipelineService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -20,12 +17,9 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.CANCELED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.FAILED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.SKIPPED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.SUCCESS;
import static dev.struchkov.bot.gitlab.context.domain.event.NewPipelineEvent.newPipeline;
import static dev.struchkov.bot.gitlab.context.domain.event.UpdatePipelineEvent.updatePipeline;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
/**
* Реализация сервиса для работы с пайплайнами.
@ -37,19 +31,15 @@ import static dev.struchkov.haiti.utils.Checker.checkNotNull;
@RequiredArgsConstructor
public class PipelineServiceImpl implements PipelineService {
// Статусы пайплайнов, о которых нужно уведомить
private static final Set<PipelineStatus> notificationStatus = Set.of(FAILED, SUCCESS, CANCELED, SKIPPED);
private final ApplicationEventPublisher eventPublisher;
private final NotifyService notifyService;
private final PipelineRepository repository;
private final PersonInformation personInformation;
@Override
@Transactional
public Pipeline create(@NonNull Pipeline pipeline) {
final Pipeline newPipeline = repository.save(pipeline);
notifyNewPipeline(pipeline, PipelineStatus.NULL);
eventPublisher.publishEvent(newPipeline(newPipeline));
return newPipeline;
}
@ -61,21 +51,6 @@ public class PipelineServiceImpl implements PipelineService {
.collect(Collectors.toList());
}
private void notifyNewPipeline(Pipeline pipeline, PipelineStatus oldStatus) {
if (isNeedNotifyNewPipeline(pipeline)) {
notifyService.send(
PipelineNotify.builder()
.projectId(pipeline.getProjectId())
.newStatus(pipeline.getStatus())
.pipelineId(pipeline.getId())
.refName(pipeline.getRef())
.webUrl(pipeline.getWebUrl())
.oldStatus(oldStatus)
.build()
);
}
}
@Override
public Pipeline update(@NonNull Pipeline pipeline) {
final Pipeline oldPipeline = repository.findById(pipeline.getId())
@ -83,12 +58,8 @@ public class PipelineServiceImpl implements PipelineService {
pipeline.setProjectId(oldPipeline.getProjectId());
if (!oldPipeline.getUpdated().equals(pipeline.getUpdated())) {
notifyNewPipeline(pipeline, oldPipeline.getStatus());
return repository.save(pipeline);
}
return oldPipeline;
eventPublisher.publishEvent(updatePipeline(oldPipeline.toBuilder().build(), pipeline));
return repository.save(pipeline);
}
@Override
@ -98,13 +69,6 @@ public class PipelineServiceImpl implements PipelineService {
.collect(Collectors.toList());
}
private boolean isNeedNotifyNewPipeline(@NonNull Pipeline pipeline) {
final Person personPipelineCreator = pipeline.getPerson();
return notificationStatus.contains(pipeline.getStatus()) // Пайплайн имеет статус необходимый для уведомления
&& checkNotNull(personPipelineCreator) // Создатель пайплайна не null
&& personInformation.getId().equals(personPipelineCreator.getId()); // Пользователь приложения является инициатором пайплайна
}
@Override
public List<Pipeline> getAllByStatuses(@NonNull Set<PipelineStatus> statuses) {
return repository.findAllByStatuses(statuses);
@ -128,7 +92,7 @@ public class PipelineServiceImpl implements PipelineService {
@Override
public void cleanOld() {
log.debug("Старт очистки старых пайплайнов");
repository.deleteByCreatedBefore(LocalDateTime.now().minusDays(1L));
repository.deleteByCreatedBefore(LocalDateTime.now().minusDays(7L));
log.debug("Конец очистки старых пайплайнов");
}

View File

@ -1,14 +1,12 @@
package dev.struchkov.bot.gitlab.core.service.impl;
package dev.struchkov.bot.gitlab.core.service;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import dev.struchkov.bot.gitlab.context.domain.notify.project.NewProjectNotify;
import dev.struchkov.bot.gitlab.context.repository.ProjectRepository;
import dev.struchkov.bot.gitlab.context.service.NotifyService;
import dev.struchkov.bot.gitlab.context.service.PersonService;
import dev.struchkov.bot.gitlab.context.service.ProjectService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -17,6 +15,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.event.NewProjectEvent.newProject;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
/**
@ -28,8 +27,7 @@ public class ProjectServiceImpl implements ProjectService {
private final ProjectRepository repository;
private final NotifyService notifyService;
private final PersonService personService;
private final ApplicationEventPublisher eventPublisher;
@Override
@Transactional
@ -37,8 +35,7 @@ public class ProjectServiceImpl implements ProjectService {
final Project newProject = repository.save(project);
if (sendNotify) {
final String authorName = personService.getByIdOrThrown(newProject.getCreatorId()).getName();
notifyAboutNewProject(newProject, authorName);
eventPublisher.publishEvent(newProject(project));
}
return newProject;
@ -111,18 +108,4 @@ public class ProjectServiceImpl implements ProjectService {
repository.processing(enable, projectIds);
}
private void notifyAboutNewProject(Project newProject, String authorName) {
notifyService.send(
NewProjectNotify.builder()
.projectId(newProject.getId())
.projectDescription(newProject.getDescription())
.projectName(newProject.getName())
.projectUrl(newProject.getWebUrl())
.sshUrlToRepo(newProject.getSshUrlToRepo())
.httpUrlToRepo(newProject.getHttpUrlToRepo())
.authorName(authorName)
.build()
);
}
}

View File

@ -1,396 +0,0 @@
package dev.struchkov.bot.gitlab.core.service.impl;
import dev.struchkov.bot.gitlab.context.domain.AssigneeChanged;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.IdAndStatusPr;
import dev.struchkov.bot.gitlab.context.domain.MergeRequestState;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.context.domain.ReviewerChanged;
import dev.struchkov.bot.gitlab.context.domain.entity.Discussion;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequest;
import dev.struchkov.bot.gitlab.context.domain.entity.MergeRequestForDiscussion;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.Project;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictMrNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.ConflictResolveMrNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrForAssignee;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.NewMrForReview;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.StatusMrNotify;
import dev.struchkov.bot.gitlab.context.domain.notify.mergerequest.UpdateMrNotify;
import dev.struchkov.bot.gitlab.context.repository.MergeRequestRepository;
import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import dev.struchkov.bot.gitlab.context.service.NotifyService;
import dev.struchkov.bot.gitlab.context.service.ProjectService;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.MergeRequestState.CLOSED;
import static dev.struchkov.bot.gitlab.context.domain.MergeRequestState.MERGED;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
import static java.lang.Boolean.TRUE;
@Slf4j
@Service
@RequiredArgsConstructor
public class MergeRequestsServiceImpl implements MergeRequestsService {
public static final Set<MergeRequestState> DELETE_STATES = Set.of(MERGED, CLOSED);
private final MergeRequestRepository repository;
private final NotifyService notifyService;
private final ProjectService projectService;
private final DiscussionService discussionService;
private final PersonInformation personInformation;
@Override
@Transactional
public MergeRequest create(@NonNull MergeRequest mergeRequest) {
final boolean botUserReviewer = isBotUserReviewer(mergeRequest);
final boolean botUserAssignee = isBotUserAssigneeAndNotAuthor(mergeRequest);
final boolean botUserAuthor = personInformation.getId().equals(mergeRequest.getAuthor().getId());
mergeRequest.setNotification(botUserReviewer || botUserAssignee || botUserAuthor);
mergeRequest.setUserAssignee(botUserAssignee);
mergeRequest.setUserReviewer(botUserReviewer);
final MergeRequest savedMergeRequest = repository.save(mergeRequest);
if (botUserReviewer || botUserAssignee) {
if (!mergeRequest.isConflict()) {
final String projectName = projectService.getByIdOrThrow(savedMergeRequest.getProjectId()).getName();
if (botUserReviewer) sendNotifyNewMrReview(savedMergeRequest, projectName);
if (botUserAssignee) sendNotifyNewAssignee(mergeRequest, projectName, null);
}
}
return savedMergeRequest;
}
private boolean isBotUserAssigneeAndNotAuthor(MergeRequest mergeRequest) {
final Long gitlabUserId = personInformation.getId();
final Person assignee = mergeRequest.getAssignee();
final Person author = mergeRequest.getAuthor();
if (checkNotNull(assignee)) {
if (gitlabUserId.equals(assignee.getId()) && !isAuthorSameAssignee(author, assignee)) {
return true;
}
}
return false;
}
/**
* Создатель MR является ответственным за этот MR
*
* @return true, если автор и ответственный один и тот же человек.
*/
private boolean isAuthorSameAssignee(Person author, Person assignee) {
return author.getId().equals(assignee.getId());
}
private boolean isBotUserReviewer(MergeRequest savedMergeRequest) {
final List<Person> reviewers = savedMergeRequest.getReviewers();
final Long botUserGitlabId = personInformation.getId();
if (checkNotEmpty(reviewers)) {
for (Person reviewer : reviewers) {
if (botUserGitlabId.equals(reviewer.getId())) {
return true;
}
}
}
return false;
}
private void sendNotifyNewMrReview(MergeRequest mergeRequest, String projectName) {
notifyService.send(
NewMrForReview.builder()
.mrId(mergeRequest.getId())
.projectName(projectName)
.labels(mergeRequest.getLabels())
.author(mergeRequest.getAuthor().getName())
.description(mergeRequest.getDescription())
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.targetBranch(mergeRequest.getTargetBranch())
.sourceBranch(mergeRequest.getSourceBranch())
.assignee(mergeRequest.getAssignee().getName())
.build()
);
}
private void sendNotifyNewAssignee(MergeRequest mergeRequest, String projectName, String oldAssigneeName) {
final NewMrForAssignee.NewMrForAssigneeBuilder builder = NewMrForAssignee.builder()
.mrId(mergeRequest.getId())
.projectName(projectName)
.labels(mergeRequest.getLabels())
.author(mergeRequest.getAuthor().getName())
.description(mergeRequest.getDescription())
.title(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.targetBranch(mergeRequest.getTargetBranch())
.sourceBranch(mergeRequest.getSourceBranch())
.reviewers(mergeRequest.getReviewers().stream().map(Person::getName).toList());
if (checkNotNull(oldAssigneeName)) {
builder.oldAssigneeName(oldAssigneeName);
if (checkNotNull(mergeRequest.getAssignee())) {
builder.newAssigneeName(mergeRequest.getAssignee().getName());
}
}
notifyService.send(builder.build());
}
@Override
@Transactional
public MergeRequest update(@NonNull MergeRequest mergeRequest) {
final MergeRequest oldMergeRequest = repository.findById(mergeRequest.getId())
.orElseThrow(notFoundException("MergeRequest не найден"));
mergeRequest.setNotification(oldMergeRequest.isNotification());
final Long gitlabUserId = personInformation.getId();
final AssigneeChanged assigneeChanged = AssigneeChanged.valueOf(gitlabUserId, oldMergeRequest.getAssignee(), mergeRequest.getAssignee());
final ReviewerChanged reviewerChanged = ReviewerChanged.valueOf(gitlabUserId, oldMergeRequest.getReviewers(), mergeRequest.getReviewers());
mergeRequest.setUserAssignee(assigneeChanged.getNewStatus(oldMergeRequest.isUserAssignee()));
mergeRequest.setUserReviewer(reviewerChanged.getNewStatus(oldMergeRequest.isUserReviewer()));
final boolean isChangedMr = !oldMergeRequest.getUpdatedDate().equals(mergeRequest.getUpdatedDate()) || oldMergeRequest.isConflict() != mergeRequest.isConflict();
final boolean isChangedLinkedEntity = reviewerChanged.isChanged() || assigneeChanged.isChanged();
if (isChangedMr || isChangedLinkedEntity) {
if (oldMergeRequest.isNotification()) {
final Project project = projectService.getByIdOrThrow(mergeRequest.getProjectId());
if (isChangedMr) {
notifyAboutStatus(oldMergeRequest, mergeRequest, project);
notifyAboutNewConflict(oldMergeRequest, mergeRequest, project);
notifyAboutResolveConflict(oldMergeRequest, mergeRequest, project);
notifyAboutUpdate(oldMergeRequest, mergeRequest, project);
}
if (isChangedLinkedEntity) {
notifyReviewer(reviewerChanged, mergeRequest, project);
notifyAssignee(assigneeChanged, oldMergeRequest, mergeRequest, project);
}
}
return repository.save(mergeRequest);
}
return oldMergeRequest;
}
//TODO [05.12.2022|uPagge]: Добавить уведомление, если происходит удаление
private void notifyAssignee(AssigneeChanged assigneeChanged, MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
switch (assigneeChanged) {
case BECOME ->
sendNotifyNewAssignee(mergeRequest, project.getName(), Optional.ofNullable(oldMergeRequest.getAssignee()).map(Person::getName).orElse(null));
}
}
//TODO [05.12.2022|uPagge]: Добавить уведомление, если происходит удаление ревьювера
private void notifyReviewer(ReviewerChanged reviewerChanged, MergeRequest mergeRequest, Project project) {
switch (reviewerChanged) {
case BECOME -> sendNotifyNewMrReview(mergeRequest, project.getName());
}
}
@Override
@Transactional
public List<MergeRequest> updateAll(@NonNull List<MergeRequest> mergeRequests) {
return mergeRequests.stream()
.map(this::update)
.collect(Collectors.toList());
}
@Override
public Set<IdAndStatusPr> getAllId(Set<MergeRequestState> statuses) {
return repository.findAllIdByStateIn(statuses);
}
@Override
public List<MergeRequestForDiscussion> getAllForDiscussion() {
return repository.findAllForDiscussion();
}
@Override
public ExistContainer<MergeRequest, Long> existsById(@NonNull Set<Long> mergeRequestIds) {
final List<MergeRequest> existsEntity = repository.findAllById(mergeRequestIds);
final Set<Long> existsIds = existsEntity.stream().map(MergeRequest::getId).collect(Collectors.toSet());
if (existsIds.containsAll(mergeRequestIds)) {
return ExistContainer.allFind(existsEntity);
} else {
final Set<Long> noExistsId = mergeRequestIds.stream()
.filter(id -> !existsIds.contains(id))
.collect(Collectors.toSet());
return ExistContainer.notAllFind(existsEntity, noExistsId);
}
}
@Override
public List<MergeRequest> createAll(List<MergeRequest> newMergeRequests) {
return newMergeRequests.stream()
.map(this::create)
.toList();
}
@Override
public List<MergeRequest> getAllByReviewerId(@NonNull Long personId) {
return repository.findAllByReviewerId(personId);
}
@Override
public void cleanOld() {
log.debug("Старт очистки старых MR");
repository.deleteByStates(DELETE_STATES);
log.debug("Конец очистки старых MR");
}
@Override
@Transactional(readOnly = true)
public Set<Long> getAllIds() {
return repository.findAllIds();
}
@Override
@Transactional
public void notification(boolean enable, @NonNull Long mrId) {
repository.notification(enable, mrId);
}
@Override
@Transactional
public void notificationByProjectId(boolean enable, @NonNull Set<Long> projectIds) {
repository.notificationByProjectId(enable, projectIds);
}
private void notifyAboutUpdate(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long botUserGitlabId = personInformation.getId();
if (
!botUserGitlabId.equals(mergeRequest.getAuthor().getId()) // Автор MR не пользователь приложения
&& !oldMergeRequest.getDateLastCommit().equals(mergeRequest.getDateLastCommit()) // Изменилась дата последнего коммита
&& !mergeRequest.isConflict() // MR не находится в состоянии конфликта
&& !botUserGitlabId.equals(oldMergeRequest.getAuthor().getId()) // и MR создан НЕ пользователем бота
) {
long allTask = 0;
long resolvedTask = 0;
long allYouTasks = 0;
long resolvedYouTask = 0;
final List<Discussion> discussions = discussionService.getAllByMergeRequestId(oldMergeRequest.getId());
for (Discussion discussion : discussions) {
if (checkNotNull(discussion.getResponsible())) {
final boolean isBotUserAuthorDiscussion = botUserGitlabId.equals(discussion.getFirstNote().getAuthor().getId());
allTask += 1;
if (isBotUserAuthorDiscussion) {
allYouTasks += 1;
}
if (TRUE.equals(discussion.getResolved())) {
resolvedTask += 1;
if (isBotUserAuthorDiscussion) {
resolvedYouTask += 1;
}
}
}
}
final UpdateMrNotify.UpdateMrNotifyBuilder notifyBuilder = UpdateMrNotify.builder()
.mrId(oldMergeRequest.getId())
.author(oldMergeRequest.getAuthor().getName())
.name(oldMergeRequest.getTitle())
.projectName(project.getName())
.url(oldMergeRequest.getWebUrl())
.allTasks(allTask)
.allResolvedTasks(resolvedTask)
.personTasks(allYouTasks)
.personResolvedTasks(resolvedYouTask);
if (oldMergeRequest.isConflict() && !mergeRequest.isConflict()) {
notifyBuilder.comment("The conflict has been resolved");
}
notifyService.send(notifyBuilder.build());
}
}
protected void notifyAboutNewConflict(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long gitlabUserId = personInformation.getId();
if (
!oldMergeRequest.isConflict() // У старого MR не было конфликта
&& mergeRequest.isConflict() // А у нового есть
&& gitlabUserId.equals(oldMergeRequest.getAuthor().getId()) // и MR создан пользователем бота
) {
notifyService.send(
ConflictMrNotify.builder()
.mrId(oldMergeRequest.getId())
.sourceBranch(oldMergeRequest.getSourceBranch())
.name(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.projectKey(project.getName())
.build()
);
}
}
private void notifyAboutResolveConflict(MergeRequest oldMergeRequest, MergeRequest mergeRequest, Project project) {
final Long gitlabUserId = personInformation.getId();
if (oldMergeRequest.isConflict() && !mergeRequest.isConflict()) {
// проверяем даты коммитов, так как при пуше в target ветку MR у которого есть конфликт, конфликт на время пропадает. Судя по всему GitLab после пуша заново проверяет вероятность конфликта. Чаще всего конфликт никуда не девается.
if (oldMergeRequest.getDateLastCommit().equals(mergeRequest.getDateLastCommit())) {
mergeRequest.setConflict(true);
} else {
if (gitlabUserId.equals(oldMergeRequest.getAuthor().getId())) {
notifyService.send(
ConflictResolveMrNotify.builder()
.mrId(oldMergeRequest.getId())
.sourceBranch(oldMergeRequest.getSourceBranch())
.name(mergeRequest.getTitle())
.url(mergeRequest.getWebUrl())
.projectKey(project.getName())
.build()
);
}
}
}
}
protected void notifyAboutStatus(MergeRequest oldMergeRequest, MergeRequest newMergeRequest, Project project) {
final MergeRequestState oldStatus = oldMergeRequest.getState();
final MergeRequestState newStatus = newMergeRequest.getState();
final Long gitlabUserId = personInformation.getId();
if (
!oldStatus.equals(newStatus) // статус изменился
&& gitlabUserId.equals(oldMergeRequest.getAuthor().getId()) // создатель MR является пользователем бота
) {
notifyService.send(
StatusMrNotify.builder()
.mrId(oldMergeRequest.getId())
.name(newMergeRequest.getTitle())
.url(oldMergeRequest.getWebUrl())
.projectName(project.getName())
.newStatus(newStatus)
.oldStatus(oldStatus)
.build()
);
}
}
}

View File

@ -1,166 +0,0 @@
package dev.struchkov.bot.gitlab.core.service.parser;
import dev.struchkov.bot.gitlab.context.domain.ExistContainer;
import dev.struchkov.bot.gitlab.context.domain.PipelineStatus;
import dev.struchkov.bot.gitlab.context.domain.entity.Pipeline;
import dev.struchkov.bot.gitlab.context.service.PipelineService;
import dev.struchkov.bot.gitlab.context.service.ProjectService;
import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
import dev.struchkov.bot.gitlab.core.service.parser.forktask.GetPipelineShortTask;
import dev.struchkov.bot.gitlab.core.service.parser.forktask.GetPipelineTask;
import dev.struchkov.bot.gitlab.sdk.domain.PipelineJson;
import dev.struchkov.bot.gitlab.sdk.domain.PipelineShortJson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.Collectors;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.CREATED;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.MANUAL;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.PENDING;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.PREPARING;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.RUNNING;
import static dev.struchkov.bot.gitlab.context.domain.PipelineStatus.WAITING_FOR_RESOURCE;
import static dev.struchkov.haiti.utils.Checker.checkFalse;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
import static dev.struchkov.haiti.utils.concurrent.ForkJoinUtils.pullTaskResult;
import static dev.struchkov.haiti.utils.concurrent.ForkJoinUtils.pullTaskResults;
/**
* Парсер пайплайнов.
*
* @author upagge 17.01.2021
*/
@Slf4j
@Service
public class PipelineParser {
private static final Set<PipelineStatus> oldStatus = Set.of(
CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING, RUNNING, MANUAL
);
private final PipelineService pipelineService;
private final ProjectService projectService;
private final GitlabProperty gitlabProperty;
private final PersonProperty personProperty;
private final ConversionService conversionService;
private final ForkJoinPool forkJoinPool;
private LocalDateTime lastUpdate = LocalDateTime.now();
public PipelineParser(
PipelineService pipelineService,
ProjectService projectService,
GitlabProperty gitlabProperty,
PersonProperty personProperty,
ConversionService conversionService,
@Qualifier("parserPool") ForkJoinPool forkJoinPool
) {
this.pipelineService = pipelineService;
this.projectService = projectService;
this.gitlabProperty = gitlabProperty;
this.personProperty = personProperty;
this.conversionService = conversionService;
this.forkJoinPool = forkJoinPool;
}
public void scanNewPipeline() {
log.debug("Старт обработки новых пайплайнов");
final Set<Long> projectIds = projectService.getAllIdByProcessingEnable();
final Map<Long, Long> pipelineProjectMap = getPipelineShortJsons(projectIds).stream()
.collect(Collectors.toMap(PipelineShortJson::getId, PipelineShortJson::getProjectId));
if (checkNotEmpty(pipelineProjectMap)) {
final ExistContainer<Pipeline, Long> existContainer = pipelineService.existsById(pipelineProjectMap.keySet());
if (checkFalse(existContainer.isAllFound())) {
final Set<Long> idsNotFound = existContainer.getIdNoFound();
final List<Pipeline> newPipelines = getNewPipelines(pipelineProjectMap, idsNotFound);
if (checkNotEmpty(newPipelines)) {
pipelineService.createAll(newPipelines);
}
}
}
log.debug("Конец обработки новых пайплайнов");
}
private List<Pipeline> getNewPipelines(Map<Long, Long> pipelineProjectMap, Set<Long> idsNotFound) {
final List<ForkJoinTask<Optional<PipelineJson>>> tasks = idsNotFound.stream()
.map(pipelineId -> new GetPipelineTask(
gitlabProperty.getPipelineUrl(),
pipelineProjectMap.get(pipelineId),
pipelineId,
personProperty.getToken()
))
.map(forkJoinPool::submit)
.collect(Collectors.toList());
return pullTaskResult(tasks).stream()
.flatMap(Optional::stream)
.map(json -> conversionService.convert(json, Pipeline.class))
.collect(Collectors.toList());
}
private List<PipelineShortJson> getPipelineShortJsons(Set<Long> projectIds) {
LocalDateTime newLastUpdate = LocalDateTime.now();
final List<ForkJoinTask<List<PipelineShortJson>>> tasks = projectIds.stream()
.map(projectId -> new GetPipelineShortTask(
gitlabProperty.getPipelinesUrl(),
projectId,
lastUpdate,
personProperty.getToken()
))
.map(forkJoinPool::submit)
.collect(Collectors.toList());
final List<PipelineShortJson> pipelineJsons = pullTaskResults(tasks);
lastUpdate = newLastUpdate;
return pipelineJsons;
}
public void scanOldPipeline() {
log.debug("Старт обработки старых пайплайнов");
final List<Pipeline> pipelines = pipelineService.getAllByStatuses(oldStatus);
final List<ForkJoinTask<Optional<PipelineJson>>> tasks = pipelines.stream()
.map(
pipeline -> new GetPipelineTask(
gitlabProperty.getPipelineUrl(),
pipeline.getProjectId(),
pipeline.getId(),
personProperty.getToken()
)
)
.map(forkJoinPool::submit)
.collect(Collectors.toList());
final List<Pipeline> newPipelines = pullTaskResult(tasks).stream()
.flatMap(Optional::stream)
.map(json -> conversionService.convert(json, Pipeline.class))
.collect(Collectors.toList());
if (checkNotEmpty(newPipelines)) {
pipelineService.updateAll(newPipelines);
}
log.debug("Конец обработки старых пайплайнов");
}
}

View File

@ -1,49 +0,0 @@
package dev.struchkov.bot.gitlab.core.service.parser.forktask;
import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import dev.struchkov.bot.gitlab.sdk.domain.DiscussionJson;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import java.text.MessageFormat;
import java.util.List;
import java.util.concurrent.RecursiveTask;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
import static dev.struchkov.bot.gitlab.core.utils.StringUtils.H_PRIVATE_TOKEN;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@AllArgsConstructor
@RequiredArgsConstructor
public class GetAllDiscussionForMergeRequestTask extends RecursiveTask<List<DiscussionJson>> {
private static final int PAGE_COUNT = 100;
private final String discussionsUrl;
private final long projectId;
private final long mergeRequestTwoId;
private final String personalGitlabToken;
private int page = 1;
@Override
@SneakyThrows
protected List<DiscussionJson> compute() {
Thread.sleep(100);
final List<DiscussionJson> jsons = getDiscussionJson();
if (checkNotEmpty(jsons) && jsons.size() == PAGE_COUNT) {
final var newTask = new GetAllDiscussionForMergeRequestTask(discussionsUrl, projectId, mergeRequestTwoId, personalGitlabToken, page + 1);
newTask.fork();
jsons.addAll(newTask.join());
}
return jsons;
}
private List<DiscussionJson> getDiscussionJson() {
return HttpParse.request(MessageFormat.format(discussionsUrl, projectId, mergeRequestTwoId, page, PAGE_COUNT))
.header(ACCEPT)
.header(H_PRIVATE_TOKEN, personalGitlabToken)
.executeList(DiscussionJson.class);
}
}

View File

@ -1,52 +0,0 @@
package dev.struchkov.bot.gitlab.core.service.parser.forktask;
import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import dev.struchkov.bot.gitlab.core.utils.StringUtils;
import dev.struchkov.bot.gitlab.sdk.domain.MergeRequestJson;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.text.MessageFormat;
import java.util.List;
import java.util.concurrent.RecursiveTask;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
import static dev.struchkov.haiti.utils.Checker.checkNotEmpty;
@Slf4j
@AllArgsConstructor
@RequiredArgsConstructor
public class GetAllMergeRequestForProjectTask extends RecursiveTask<List<MergeRequestJson>> {
private static final int PAGE_COUNT = 100;
private final long projectId;
private int pageNumber = 1;
private final String urlMrOpen;
private final String gitlabToken;
@Override
@SneakyThrows
protected List<MergeRequestJson> compute() {
Thread.sleep(100);
final List<MergeRequestJson> mergeRequestJsons = getMergeRequestJsons();
if (checkNotEmpty(mergeRequestJsons) && mergeRequestJsons.size() == PAGE_COUNT) {
final GetAllMergeRequestForProjectTask newTask = new GetAllMergeRequestForProjectTask(projectId, pageNumber + 1, urlMrOpen, gitlabToken);
newTask.fork();
mergeRequestJsons.addAll(newTask.join());
}
return mergeRequestJsons;
}
private List<MergeRequestJson> getMergeRequestJsons() {
final List<MergeRequestJson> jsons = HttpParse.request(MessageFormat.format(urlMrOpen, projectId, pageNumber, PAGE_COUNT))
.header(StringUtils.H_PRIVATE_TOKEN, gitlabToken)
.header(ACCEPT)
.executeList(MergeRequestJson.class);
log.trace("Получено {} шт потенциально новых MR для проекта id:'{}' ", jsons.size(), projectId);
return jsons;
}
}

View File

@ -1,55 +0,0 @@
package dev.struchkov.bot.gitlab.core.service.parser.forktask;
import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import dev.struchkov.bot.gitlab.core.utils.StringUtils;
import dev.struchkov.bot.gitlab.sdk.domain.PipelineShortJson;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.RecursiveTask;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
@Slf4j
@AllArgsConstructor
@RequiredArgsConstructor
public class GetPipelineShortTask extends RecursiveTask<List<PipelineShortJson>> {
private static final int PAGE_COUNT = 100;
private final String urlPipelines;
private final long projectId;
private int pageNumber = 1;
private final LocalDateTime lastUpdate;
private final String gitlabToken;
@Override
@SneakyThrows
protected List<PipelineShortJson> compute() {
Thread.sleep(100);
final List<PipelineShortJson> jsons = getPipelineJsons();
if (jsons.size() == PAGE_COUNT) {
final GetPipelineShortTask newTask = new GetPipelineShortTask(urlPipelines, projectId, pageNumber + 1, lastUpdate, gitlabToken);
newTask.fork();
jsons.addAll(newTask.join());
}
jsons.forEach(pipelineJson -> pipelineJson.setProjectId(projectId));
return jsons;
}
private List<PipelineShortJson> getPipelineJsons() {
final List<PipelineShortJson> jsons = HttpParse.request(MessageFormat.format(urlPipelines, projectId, pageNumber, PAGE_COUNT))
.header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, gitlabToken)
.getParameter("updated_after", lastUpdate.minusHours(12L).toString())
.executeList(PipelineShortJson.class);
log.trace("Получено {} шт потенциально новых пайплайнов для проекта id:'{}' ", jsons.size(), projectId);
return jsons;
}
}

View File

@ -1,40 +0,0 @@
package dev.struchkov.bot.gitlab.core.service.parser.forktask;
import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import dev.struchkov.bot.gitlab.core.utils.StringUtils;
import dev.struchkov.bot.gitlab.sdk.domain.PipelineJson;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.text.MessageFormat;
import java.util.Optional;
import java.util.concurrent.RecursiveTask;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
@Slf4j
@RequiredArgsConstructor
public class GetPipelineTask extends RecursiveTask<Optional<PipelineJson>> {
private final String urlPipeline;
private final long projectId;
private final long pipelineId;
private final String gitlabToken;
@Override
@SneakyThrows
protected Optional<PipelineJson> compute() {
Thread.sleep(100);
return HttpParse.request(MessageFormat.format(urlPipeline, projectId, pipelineId))
.header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, gitlabToken)
.execute(PipelineJson.class)
.map(json -> {
json.setProjectId(projectId);
return json;
});
}
}

View File

@ -1,37 +0,0 @@
package dev.struchkov.bot.gitlab.core.service.parser.forktask;
import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import dev.struchkov.bot.gitlab.core.utils.StringUtils;
import dev.struchkov.bot.gitlab.sdk.domain.MergeRequestJson;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.text.MessageFormat;
import java.util.Optional;
import java.util.concurrent.RecursiveTask;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
@Slf4j
@RequiredArgsConstructor
public class GetSingleMergeRequestTask extends RecursiveTask<Optional<MergeRequestJson>> {
private final String urlMr;
private final long projectId;
private final long mrTwoId;
private final String gitlabToken;
@Override
@SneakyThrows
protected Optional<MergeRequestJson> compute() {
Thread.sleep(100);
final String mrUrl = MessageFormat.format(urlMr, projectId, mrTwoId);
return HttpParse.request(mrUrl)
.header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, gitlabToken)
.execute(MergeRequestJson.class);
}
}

View File

@ -1,33 +0,0 @@
package dev.struchkov.bot.gitlab.core.utils;
import static dev.struchkov.haiti.utils.Inspector.isNotNull;
/**
* Утилитарная сущность для {@link HttpParse}. Упрощает сохранения в константы заголовков для запроса.
*
* @author upagge 23.12.2020
*/
public class HttpHeader {
private final String name;
private final String value;
private HttpHeader(String name, String value) {
this.name = name;
this.value = value;
}
public static HttpHeader of(String name, String value) {
isNotNull(name, value);
return new HttpHeader(name, value);
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
}

View File

@ -1,114 +0,0 @@
package dev.struchkov.bot.gitlab.core.utils;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.struchkov.haiti.utils.Inspector;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static dev.struchkov.haiti.utils.Checker.checkNotNull;
import static dev.struchkov.haiti.utils.Inspector.isNotNull;
/**
* Утилитарный класс для работы с web.
*
* @author upagge 30.09.2020
*/
public class HttpParse {
private static final Logger log = LoggerFactory.getLogger(HttpParse.class);
public static final HttpHeader ACCEPT = HttpHeader.of("Accept", "text/html,application/xhtml+xml,application/json");
private static final ObjectMapper objectMapper;
private final Request.Builder requestBuilder = new Request.Builder();
private final HttpUrl.Builder httpUrlBuilder;
static {
objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
public HttpParse(String url) {
Inspector.isNotNull(url);
httpUrlBuilder = HttpUrl.parse(url).newBuilder();
}
public static HttpParse request(String url) {
Inspector.isNotNull(url);
return new HttpParse(url);
}
public HttpParse header(String name, String value) {
isNotNull(name);
if (value != null) {
requestBuilder.header(name, value);
}
return this;
}
public HttpParse header(HttpHeader header) {
isNotNull(header);
requestBuilder.header(header.getName(), header.getValue());
return this;
}
public HttpParse getParameter(String name, String value) {
isNotNull(name);
if (value != null) {
httpUrlBuilder.addQueryParameter(name, value);
}
return this;
}
public <T> Optional<T> execute(Class<T> classOfT) {
isNotNull(classOfT);
final HttpUrl url = httpUrlBuilder.build();
final Request request = requestBuilder.url(url).build();
log.trace("Выполняется okhttp3 запрос | {}", url);
final OkHttpClient httpClient = new OkHttpClient();
try (final Response execute = httpClient.newCall(request).execute()) {
log.trace("Запрос выполнен | {}", url);
if (execute.isSuccessful() && checkNotNull(execute.body())) {
final String string = execute.body().string();
return Optional.ofNullable(objectMapper.readValue(string, classOfT));
}
} catch (IOException e) {
log.error("Ошибка выполнения okhttp3", e);
}
return Optional.empty();
}
//TODO [16.01.2023|uPagge]: Okhttp Client создается на каждый запрос, что не рационально по потреблению ресурсов и производительности, но позволяет обойти ограничение со стороны гитлаба, при котором один и тот же клиент отбрасывался спустя 1000 запросов. Возможно стоит заменить OkHttp на что-то другое, например, RestTemplate
public <T> List<T> executeList(Class<T> classOfT) {
isNotNull(classOfT);
final HttpUrl url = httpUrlBuilder.build();
final Request request = requestBuilder.url(url).build();
log.trace("Выполняется okhttp3 запрос | {}", url);
final OkHttpClient httpClient = new OkHttpClient();
try (Response execute = httpClient.newCall(request).execute()) {
log.trace("Запрос выполнен | {}", url);
ResponseBody body = execute.body();
if (execute.isSuccessful() && checkNotNull(body)) {
final String stringBody = body.string();
final List<T> list = objectMapper.readValue(stringBody, objectMapper.getTypeFactory().constructCollectionType(List.class, classOfT));
return (list == null || list.isEmpty()) ? Collections.emptyList() : list;
}
} catch (IOException e) {
log.error("Ошибка выполнения okhttp3", e);
}
return Collections.emptyList();
}
}

View File

@ -1,61 +0,0 @@
package dev.struchkov.bot.gitlab.core.utils;
import okhttp3.OkHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import static dev.struchkov.haiti.utils.Exceptions.utilityClass;
public class OkHttpUtil {
private static final Logger log = LoggerFactory.getLogger(OkHttpUtil.class);
public OkHttpUtil() {
utilityClass();
}
public static void ignoreCertificate(OkHttpClient.Builder builder) {
log.info("Initialising httpUtil with default configuration");
configureToIgnoreCertificate(builder);
}
//Setting testMode configuration. If set as testMode, the connection will skip certification check
private static void configureToIgnoreCertificate(OkHttpClient.Builder builder) {
log.warn("Ignore Ssl Certificate");
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
};
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
builder.hostnameVerifier((hostname, session) -> true);
} catch (Exception e) {
log.warn("Exception while configuring IgnoreSslCertificate" + e, e);
}
}
}

View File

@ -1,16 +0,0 @@
package dev.struchkov.bot.gitlab.core.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/**
* Утилитарный класс для работы со сторками.
*
* @author upagge 29.09.2020
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StringUtils {
public static final String H_PRIVATE_TOKEN = "PRIVATE-TOKEN";
}

View File

@ -4,7 +4,7 @@
<parent>
<groupId>dev.struchkov.bot.gitlab</groupId>
<artifactId>gitlab-bot</artifactId>
<version>1.0.0</version>
<version>2.0.0-SNAPSHOT</version>
</parent>
<artifactId>bot-data</artifactId>

View File

@ -1,8 +1,10 @@
package dev.struchkov.bot.gitlab.data.impl;
import dev.struchkov.bot.gitlab.context.domain.entity.Person;
import dev.struchkov.bot.gitlab.context.domain.entity.PersonTelegram;
import dev.struchkov.bot.gitlab.context.repository.PersonRepository;
import dev.struchkov.bot.gitlab.data.jpa.PersonJpaRepository;
import dev.struchkov.bot.gitlab.data.jpa.PersonTelegramRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
@ -18,6 +20,7 @@ import java.util.Set;
public class PersonRepositoryImpl implements PersonRepository {
private final PersonJpaRepository jpaRepository;
private final PersonTelegramRepository jpaTelegramRepository;
@Override
public Person save(Person person) {
@ -34,4 +37,14 @@ public class PersonRepositoryImpl implements PersonRepository {
return jpaRepository.findAllById(personIds);
}
@Override
public Optional<PersonTelegram> findTelegramInfoById(Long personId) {
return jpaTelegramRepository.findById(personId);
}
@Override
public List<PersonTelegram> findAllTelegramInfoByIds(Set<Long> personIds) {
return jpaTelegramRepository.findAllById(personIds);
}
}

View File

@ -8,4 +8,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
*/
public interface PersonJpaRepository extends JpaRepository<Person, Long> {
}

View File

@ -0,0 +1,8 @@
package dev.struchkov.bot.gitlab.data.jpa;
import dev.struchkov.bot.gitlab.context.domain.entity.PersonTelegram;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PersonTelegramRepository extends JpaRepository<PersonTelegram, Long> {
}

View File

@ -16,7 +16,7 @@ hide:
- Банковская карта: [https://www.tinkoff.ru/cf/4iU6NB3uzqx](https://www.tinkoff.ru/cf/4iU6NB3uzqx)
- Крипта:
* TON: struchkov-mark.ton
* BTC (Taproot): `bc1pt49vnp43c4mktk6309zlq3020dzd0p89gc8d90zzn4sgjvck56xs0t86vy`
* ETH: `0x7668C802Bd71Be965671D4Bbb1AD90C7f7f32921`
* USDT (ERC-20): `0x7668C802Bd71Be965671D4Bbb1AD90C7f7f32921`

View File

@ -4,7 +4,7 @@
<parent>
<groupId>dev.struchkov.bot.gitlab</groupId>
<artifactId>gitlab-bot</artifactId>
<version>1.0.0</version>
<version>2.0.0-SNAPSHOT</version>
</parent>
<artifactId>gitlab-app</artifactId>
@ -30,8 +30,9 @@
</dependency>
<dependency>
<groupId>dev.struchkov.bot.gitlab</groupId>
<artifactId>gitlab-sdk</artifactId>
<groupId>dev.struchkov.sdk.gitlab</groupId>
<artifactId>gitlab-sdk-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>

View File

@ -1,21 +1,31 @@
package dev.struchkov.bot.gitlab.config;
import dev.struchkov.bot.gitlab.context.domain.PersonInformation;
import dev.struchkov.bot.gitlab.core.config.properties.GitlabProperty;
import dev.struchkov.bot.gitlab.core.config.properties.PersonProperty;
import dev.struchkov.bot.gitlab.core.utils.HttpParse;
import dev.struchkov.bot.gitlab.core.utils.StringUtils;
import dev.struchkov.bot.gitlab.context.prop.AppProperty;
import dev.struchkov.bot.gitlab.context.prop.GroupNotifyProperty;
import dev.struchkov.bot.gitlab.context.prop.PersonProperty;
import dev.struchkov.godfather.simple.domain.BoxAnswer;
import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending;
import dev.struchkov.sdk.gitlab.core.GitlabSdkManager;
import dev.struchkov.sdk.gitlab.schema.common.PersonJson;
import jakarta.persistence.EntityManagerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.scheduling.annotation.EnableScheduling;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.ForkJoinPool;
import static dev.struchkov.bot.gitlab.core.utils.HttpParse.ACCEPT;
import static dev.struchkov.haiti.context.exception.NotFoundException.notFoundException;
import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.ENABLE_MARKDOWN;
/**
* Общий файл настройки всего приложения.
@ -36,6 +46,45 @@ public class AppConfig {
// return taskScheduler;
// }
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("dev.struchkov.bot.gitlab");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
// Explicitly set the EntityManagerFactory interface to avoid conflict between
// the EntityManagerFactory interfaces used by Spring and Hibernate.
em.setEntityManagerFactoryInterface(EntityManagerFactory.class);
return em;
}
@Bean
@ConfigurationProperties(prefix = "gitlab-bot")
public AppProperty appProperty() {
return new AppProperty();
}
@Bean
@ConfigurationProperties(prefix = "gitlab-bot.person")
public PersonProperty personProperty() {
return new PersonProperty();
}
@Bean
@ConfigurationProperties(prefix = "gitlab-bot.group-notify")
public GroupNotifyProperty groupNotifyProperty() {
return new GroupNotifyProperty();
}
@Bean("parserPool")
public ForkJoinPool parserPool() {
return new ForkJoinPool(4);
}
@Bean
public ConversionService conversionService(Converter... converters) {
final DefaultConversionService defaultConversionService = new DefaultConversionService();
@ -45,16 +94,29 @@ public class AppConfig {
@Bean
public PersonInformation personInformation(
GitlabSdkManager gitlabSdkManager,
PersonProperty personProperty,
GitlabProperty gitlabProperty
TelegramSending telegramSender
) {
final PersonInformation personInformation = HttpParse.request(gitlabProperty.getUserUrl())
.header(ACCEPT)
.header(StringUtils.H_PRIVATE_TOKEN, personProperty.getToken())
.execute(PersonInformation.class)
.orElseThrow(notFoundException("Пользователь не найден"));
personInformation.setTelegramId(personProperty.getTelegramId());
return personInformation;
final Optional<PersonJson> optAuthPerson = gitlabSdkManager.getAuthPerson();
if (optAuthPerson.isPresent()) {
final PersonJson authUser = optAuthPerson.get();
return PersonInformation.builder()
.id(authUser.getId())
.username(authUser.getUsername())
.name(authUser.getName())
.telegramId(personProperty.getTelegramId())
.build();
} else {
telegramSender.send(
BoxAnswer.builder()
.recipientPersonId(personProperty.getTelegramId())
.message("\uD83D\uDED1 *Ошибка доступа к GitLab APi* \uD83D\uDED1\n-- -- -- -- -- --\nВозможные причины:\n1. Невалидный токен доступа.\n2. Для доступа к GitLab нужно включить VPN.")
.payload(ENABLE_MARKDOWN)
.build()
);
throw new RuntimeException("Ошибка доступа к GitLab APi.");
}
}
}

View File

@ -4,10 +4,10 @@ import dev.struchkov.bot.gitlab.context.service.AppSettingService;
import dev.struchkov.bot.gitlab.context.service.DiscussionService;
import dev.struchkov.bot.gitlab.context.service.MergeRequestsService;
import dev.struchkov.bot.gitlab.context.service.PipelineService;
import dev.struchkov.bot.gitlab.core.service.parser.DiscussionParser;
import dev.struchkov.bot.gitlab.core.service.parser.MergeRequestParser;
import dev.struchkov.bot.gitlab.core.service.parser.PipelineParser;
import dev.struchkov.bot.gitlab.core.service.parser.ProjectParser;
import dev.struchkov.bot.gitlab.core.parser.DiscussionParser;
import dev.struchkov.bot.gitlab.core.parser.MergeRequestParser;
import dev.struchkov.bot.gitlab.core.parser.PipelineParser;
import dev.struchkov.bot.gitlab.core.parser.ProjectParser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;

View File

@ -8,9 +8,9 @@ spring:
change-log: classpath:liquibase/changelog.xml
jpa:
show-sql: false
open-in-view: false
hibernate:
ddl-auto: none
database-platform: org.hibernate.dialect.PostgreSQLDialect
properties:
hibernate:
jdbc:
@ -19,7 +19,8 @@ spring:
logging:
level:
"dev.struchkov": ${LOG_LEVEL:info}
"dev.struchkov": ${LOG_LEVEL:INFO}
"dev.struchkov.bot.gitlab": ${APP_LOG_LEVEL:INFO}
telegram:
bot:
@ -36,34 +37,21 @@ telegram:
password: ${PROXY_PASSWORD:}
gitlab-bot:
version: 1.0.0
version: 2.0.0
cron:
scan:
general: "0 */1 * * * *"
new-project: "0 0 */1 * * *"
new-merge-request: "0 */15 * * * *"
general: ${CRON_GENERAL:0 */1 * * * *}
new-project: ${CRON_NEW_PROJECTS:0 0 */1 * * *}
new-merge-request: ${CRON_NEW_MR:0 */15 * * * *}
person:
telegram-id: ${TELEGRAM_PERSON_ID}
token: ${GITLAB_PERSONAL_TOKEN}
gitlab:
base-url: ${GITLAB_URL}
replaceUrl: ${GITLAB_REPLACE_URL}
users-url: "${GITLAB_URL}/api/v4/users"
user-url: "${GITLAB_URL}/api/v4/user"
projects-url: "${GITLAB_URL}/api/v4/projects?page={0, number, integer}&per_page=100"
open-merge-requests-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests?state=opened&page={1, number, integer}&per_page={2, number, integer}"
close-merge-requests-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests?state=closed&page={1, number, integer}&per_page=100"
comments-of-merge-request-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests/{1,number,#}/notes?&page={2,number,#}&per_page=100"
merge-request-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests/{1,number,#}"
project-add-url: "${GITLAB_URL}/api/v4/projects/"
note-url: "{0}#note_{1,number,#}"
notes-of-merge-request-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests/{1,number,#}/notes/{2,number,#}"
pipelines-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/pipelines?&page={1,number,#}&per_page={2,number,#}"
pipeline-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/pipelines/{1,number,#}"
last-commit-of-merge-request-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests/{1,number,#}/commits?&page=1&per_page=1"
new-note-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests/{1,number,#}/discussions/{2}/notes?body={3}"
discussions-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests/{1,number,#}/discussions?&page={2,number,#}&per_page={3,number,#}"
discussion-url: "${GITLAB_URL}/api/v4/projects/{0,number,#}/merge_requests/{1,number,#}/discussions/{2}"
group-notify:
chat-id: ${TELEGRAM_CHAT_ID:}
thread-id: ${TELEGRAM_THREAD_ID:}
gitlab-sdk:
access-token: ${GITLAB_PERSONAL_TOKEN}
base-url: ${GITLAB_URL}
replace-url: ${GITLAB_REPLACE_URL}
---
spring:

View File

@ -4,5 +4,6 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.17.xsd">
<include file="v.1.0.0/changelog.xml" relativeToChangelogFile="true"/>
<include file="v.2.0.0/changelog.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

View File

@ -0,0 +1,21 @@
<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 http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.17.xsd">
<changeSet id="2024-01-20-create-table-approvals" author="struchkov">
<createTable tableName="merge_request_approvals">
<column name="merge_request_id" type="int">
<constraints nullable="false" foreignKeyName="fk_merge_request_approvals_merge_request_id"
references="merge_request(id)" deleteCascade="true"/>
</column>
<column name="person_id" type="int">
<constraints nullable="false" foreignKeyName="fk_merge_request_approvals_person_id"
references="person(id)" deleteCascade="true"/>
</column>
</createTable>
<addPrimaryKey tableName="merge_request_approvals" columnNames="merge_request_id, person_id"/>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,34 @@
<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 http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.17.xsd">
<changeSet id="2024-02-06-change-varchar" author="struchkov">
<modifyDataType tableName="merge_request" columnName="title" newDataType="varchar"/>
<modifyDataType tableName="merge_request" columnName="description" newDataType="varchar"/>
<modifyDataType tableName="merge_request" columnName="state" newDataType="varchar"/>
<modifyDataType tableName="merge_request" columnName="web_url" newDataType="varchar"/>
<modifyDataType tableName="merge_request" columnName="source_branch" newDataType="varchar"/>
<modifyDataType tableName="merge_request" columnName="target_branch" newDataType="varchar"/>
<modifyDataType tableName="discussion" columnName="id" newDataType="varchar"/>
<modifyDataType tableName="discussion_merge_request" columnName="discussion_id" newDataType="varchar"/>
<modifyDataType tableName="merge_request_label" columnName="label" newDataType="varchar"/>
<modifyDataType tableName="note" columnName="type" newDataType="varchar"/>
<modifyDataType tableName="note" columnName="body" newDataType="varchar"/>
<modifyDataType tableName="note" columnName="noteable_type" newDataType="varchar"/>
<modifyDataType tableName="note" columnName="resolvable" newDataType="varchar"/>
<modifyDataType tableName="note" columnName="web_url" newDataType="varchar"/>
<modifyDataType tableName="person" columnName="username" newDataType="varchar"/>
<modifyDataType tableName="person" columnName="name" newDataType="varchar"/>
<modifyDataType tableName="person" columnName="web_url" newDataType="varchar"/>
<modifyDataType tableName="pipeline" columnName="ref" newDataType="varchar"/>
<modifyDataType tableName="pipeline" columnName="status" newDataType="varchar"/>
<modifyDataType tableName="pipeline" columnName="web_url" newDataType="varchar"/>
<modifyDataType tableName="project" columnName="name" newDataType="varchar"/>
<modifyDataType tableName="project" columnName="description" newDataType="varchar"/>
<modifyDataType tableName="project" columnName="web_url" newDataType="varchar"/>
<modifyDataType tableName="project" columnName="ssh_url_to_repo" newDataType="varchar"/>
<modifyDataType tableName="project" columnName="http_url_to_repo" newDataType="varchar"/>
</changeSet>
</databaseChangeLog>

Some files were not shown because too many files have changed in this diff Show More