From fefd7ef1f7c8c33a9c3fa4c8d1517e47caf01323 Mon Sep 17 00:00:00 2001 From: Azmi Touil Date: Tue, 1 Aug 2023 16:17:25 +0200 Subject: [PATCH] PR Review --- .../github/dao/GitHubHookDAO.java | 47 --- .../gamification/github/dao/WebHookDAO.java | 83 +++++ .../github/entity/GitHubHookEntity.java | 75 ----- .../github/entity/WebhookEntity.java | 79 +++++ .../github/listener/GithubEventsListener.java | 12 +- .../github/model/RemoteOrganization.java | 36 ++ .../gamification/github/model/WebHook.java | 44 +++ .../github/rest/GithubWebHookRest.java | 152 ++++----- .../github/rest/HooksManagementRest.java | 93 ++++-- .../github/rest/builder/WebHookBuilder.java | 65 ++++ .../github/rest/model/WebHookList.java | 38 +++ .../github/rest/model/WebHookRestEntity.java | 53 +++ .../services/GithubHooksManagement.java | 297 ----------------- .../github/services/WebhookService.java | 93 ++++++ .../services/impl/WebhookServiceImpl.java | 312 ++++++++++++++++++ .../github/storage/WebHookStorage.java | 87 +++++ .../github/storage/mapper/WebHookMapper.java | 64 ++++ .../gamification/github/utils/Utils.java | 9 +- .../resources/conf/portal/configuration.xml | 4 - ...ification-github-storage-configuration.xml | 16 +- .../github-connector.db.changelog-1.0.0.xml | 40 +++ .../github/BaseGithubConnectorsTest.java | 83 ----- .../resources/conf/portal/configuration.xml | 3 +- .../GitHubWebHookManagement_en.properties | 17 +- .../components/GithubAdminConnectorHook.vue | 135 ++++++++ .../GithubAdminConnectorHookList.vue | 106 ++++++ .../components/GithubAdminConnectorItem.vue | 22 +- .../components/GithubAdminHookFormDrawer.vue | 239 +++++++++++--- .../GithubAdminHookSettingDrawer.vue | 103 ------ .../extension.js | 1 + .../initComponents.js | 6 +- .../js/GithubConnectorService.js | 60 ++++ .../githubAdminConnectorExtension/services.js | 27 ++ 33 files changed, 1710 insertions(+), 791 deletions(-) delete mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/dao/GitHubHookDAO.java create mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/dao/WebHookDAO.java delete mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/entity/GitHubHookEntity.java create mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/entity/WebhookEntity.java create mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/model/RemoteOrganization.java create mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/model/WebHook.java create mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/builder/WebHookBuilder.java create mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/model/WebHookList.java create mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/model/WebHookRestEntity.java delete mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/services/GithubHooksManagement.java create mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/services/WebhookService.java create mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/services/impl/WebhookServiceImpl.java create mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/storage/WebHookStorage.java create mode 100644 gamification-github-services/src/main/java/org/exoplatform/gamification/github/storage/mapper/WebHookMapper.java delete mode 100644 gamification-github-services/src/test/java/org/exoplatform/gamification/github/BaseGithubConnectorsTest.java create mode 100644 gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminConnectorHook.vue create mode 100644 gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminConnectorHookList.vue delete mode 100644 gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminHookSettingDrawer.vue create mode 100644 gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/js/GithubConnectorService.js create mode 100644 gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/services.js diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/dao/GitHubHookDAO.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/dao/GitHubHookDAO.java deleted file mode 100644 index d0e470fa8..000000000 --- a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/dao/GitHubHookDAO.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2020 - 2022 Meeds Association contact@meeds.io - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.exoplatform.gamification.github.dao; - -import java.util.List; - -import javax.persistence.TypedQuery; - -import org.exoplatform.commons.persistence.impl.GenericDAOJPAImpl; -import org.exoplatform.gamification.github.entity.GitHubHookEntity; - -public class GitHubHookDAO extends GenericDAOJPAImpl { - - public List getHooksByExoEnvironment(String exoEnvironment) { - - TypedQuery query = getEntityManager() - .createNamedQuery("GitHubHookEntity.getHooksByExoEnvironment", - GitHubHookEntity.class) - .setParameter("exoEnvironment", exoEnvironment); - return query.getResultList(); - } - - public List getHooksByOrgRepoAndEnvironment(String org, String repo, String exoEnvironment) { - - TypedQuery query = getEntityManager() - .createNamedQuery("GitHubHookEntity.getHooksByOrgRepoAndEnvironment", - GitHubHookEntity.class) - .setParameter("org", org) - .setParameter("repo", repo) - .setParameter("exoEnvironment", exoEnvironment); - return query.getResultList(); - } - -} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/dao/WebHookDAO.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/dao/WebHookDAO.java new file mode 100644 index 000000000..1804c3ec8 --- /dev/null +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/dao/WebHookDAO.java @@ -0,0 +1,83 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * Copyright (C) 2020 - 2022 Meeds Association contact@meeds.io + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.exoplatform.gamification.github.dao; + +import org.apache.commons.collections.CollectionUtils; +import org.exoplatform.commons.persistence.impl.GenericDAOJPAImpl; +import org.exoplatform.gamification.github.entity.WebhookEntity; + +import javax.persistence.NoResultException; +import javax.persistence.Tuple; +import javax.persistence.TypedQuery; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +public class WebHookDAO extends GenericDAOJPAImpl { + + public static final String ORGANIZATION_ID = "organizationId"; + + public WebhookEntity getWebhookByOrganizationId(long organizationId) { + TypedQuery query = getEntityManager().createNamedQuery("GitHubWebhooks.getWebhookByOrganizationId", + WebhookEntity.class); + query.setParameter(ORGANIZATION_ID, organizationId); + query.setMaxResults(1); + try { + return query.getSingleResult(); + } catch (NoResultException e) { + return null; + } + } + + public List getWebhookIds(int offset, int limit) { + TypedQuery query = getEntityManager().createNamedQuery("GitHubWebhooks.getWebhookIds", Tuple.class); + List result = query.getResultList(); + if (CollectionUtils.isEmpty(result)) { + return Collections.emptyList(); + } else { + Stream resultStream = result.stream().map(tuple -> tuple.get(0, Long.class)); + if (offset > 0) { + resultStream = resultStream.skip(offset); + } + if (limit > 0) { + resultStream = resultStream.limit(limit); + } + return resultStream.toList(); + } + } + + public String getWebHookHookSecret(long organizationId) { + TypedQuery query = getEntityManager().createNamedQuery("GitHubWebhooks.getWebHookHookSecret", String.class); + query.setParameter(ORGANIZATION_ID, organizationId); + query.setMaxResults(1); + try { + return query.getSingleResult(); + } catch (NoResultException e) { + return null; + } + } + + public String getWebHookAccessToken(long organizationId) { + TypedQuery query = getEntityManager().createNamedQuery("GitHubWebhooks.getWebHookAccessToken", String.class); + query.setParameter(ORGANIZATION_ID, organizationId); + query.setMaxResults(1); + try { + return query.getSingleResult(); + } catch (NoResultException e) { + return null; + } + } +} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/entity/GitHubHookEntity.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/entity/GitHubHookEntity.java deleted file mode 100644 index 6156c685b..000000000 --- a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/entity/GitHubHookEntity.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2020 - 2022 Meeds Association contact@meeds.io - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.exoplatform.gamification.github.entity; - -import java.io.Serializable; -import java.util.Date; - -import javax.persistence.*; - -import org.exoplatform.commons.api.persistence.ExoEntity; - -import lombok.Data; - -@Entity(name = "GitHubHookEntity") -@ExoEntity -@Table(name = "GAM_GITHUB_HOOKS") -@Data -@NamedQuery( - name = "GitHubHookEntity.getHooksByExoEnvironment", - query = "SELECT hook FROM GitHubHookEntity hook where hook.exoEnvironment = :exoEnvironment " -) -@NamedQuery( - name = "GitHubHookEntity.getHooksByOrgRepoAndEnvironment", - query = "SELECT hook FROM GitHubHookEntity hook where hook.organization = :org and hook.repo = :repo and hook.exoEnvironment = :exoEnvironment" -) -public class GitHubHookEntity implements Serializable { - - private static final long serialVersionUID = -8714221800558263059L; - - @Id - @SequenceGenerator(name = "SEQ_GAM_GITHUB_HOOKS_ID", sequenceName = "SEQ_GAM_GITHUB_HOOKS_ID", allocationSize = 1) - @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_GAM_GITHUB_HOOKS_ID") - @Column(name = "ID") - protected Long id; - - @Column(name = "GITHUB_ID") - protected Long githubId; - - @Column(name = "ORGANIZATION", nullable = false) - protected String organization; - - @Column(name = "REPO", nullable = false) - protected String repo; - - @Column(name = "HOOK_URL", unique = true, nullable = false) - protected String webhook; - - @Column(name = "EVENTS", nullable = false) - protected String events; - - @Column(name = "EXO_ENVIRONMENT", nullable = false) - protected String exoEnvironment; - - @Column(name = "ENABLED", nullable = false) - protected Boolean enabled; - - @Column(name = "CREATED_DATE", nullable = false) - protected Date createdDate; - - @Column(name = "UPDATED_DATE", nullable = false) - protected Date updatedDate; -} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/entity/WebhookEntity.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/entity/WebhookEntity.java new file mode 100644 index 000000000..9561870e5 --- /dev/null +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/entity/WebhookEntity.java @@ -0,0 +1,79 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * Copyright (C) 2020 - 2023 Meeds Association contact@meeds.io + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.exoplatform.gamification.github.entity; + +import java.io.Serializable; +import java.util.Date; + +import javax.persistence.*; + +import org.exoplatform.commons.api.persistence.ExoEntity; + +import lombok.Data; + +@Entity(name = "GitHubWebhooks") +@ExoEntity +@Table(name = "GITHUB_WEBHOOKS") +@NamedQuery( + name = "GitHubWebhooks.getWebhookByOrganizationId", + query = "SELECT gitHubWebhook FROM GitHubWebhooks gitHubWebhook" + + " WHERE gitHubWebhook.organizationId = :organizationId") +@NamedQuery( + name = "GitHubWebhooks.getWebHookHookSecret", + query = "SELECT gitHubWebhook.secret FROM GitHubWebhooks gitHubWebhook" + + " WHERE gitHubWebhook.organizationId = :organizationId") +@NamedQuery( + name = "GitHubWebhooks.getWebHookAccessToken", + query = "SELECT gitHubWebhook.token FROM GitHubWebhooks gitHubWebhook" + + " WHERE gitHubWebhook.organizationId = :organizationId") +@NamedQuery( + name = "GitHubWebhooks.getWebhookIds", + query = "SELECT gitHubWebhook.id FROM GitHubWebhooks gitHubWebhook") +@Data +public class WebhookEntity implements Serializable { + + private static final long serialVersionUID = 2607146513663056421L; + + @Id + @SequenceGenerator(name = "SEQ_GITHUB_WEBHOOKS_ID", sequenceName = "SEQ_GITHUB_WEBHOOKS_ID", allocationSize = 1) + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_GITHUB_WEBHOOKS_ID") + @Column(name = "ID") + protected Long id; + + @Column(name = "WEBHOOK_ID") + protected Long webhookId; + + @Column(name = "ORGANIZATION_ID", nullable = false) + protected Long organizationId; + + @Column(name = "ENABLED", nullable = false) + protected Boolean enabled; + + @Column(name = "WATCHED_DATE", nullable = false) + protected Date watchedDate; + + @Column(name = "WATCHED_BY", nullable = false) + protected Long watchedBy; + + @Column(name = "UPDATED_DATE", nullable = false) + protected Date updatedDate; + + @Column(name = "SECRET", nullable = false) + private String secret; + + @Column(name = "TOKEN", nullable = false) + private String token; +} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/listener/GithubEventsListener.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/listener/GithubEventsListener.java index 1e79a7c75..99ff775bc 100644 --- a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/listener/GithubEventsListener.java +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/listener/GithubEventsListener.java @@ -17,24 +17,24 @@ import java.util.Map; -import org.exoplatform.gamification.github.services.GithubHooksManagement; +import org.exoplatform.gamification.github.services.impl.WebhookServiceImpl; import org.exoplatform.services.listener.Event; import org.exoplatform.services.listener.Listener; public class GithubEventsListener extends Listener, String> { - private GithubHooksManagement githubHooksManagement; + private final WebhookServiceImpl webhookService; - public GithubEventsListener(GithubHooksManagement githubHooksManagement) { - this.githubHooksManagement = githubHooksManagement; + public GithubEventsListener(WebhookServiceImpl webhookService) { + this.webhookService = webhookService; } @Override - public void onEvent(Event, String> event) throws Exception { + public void onEvent(Event, String> event) { String ruleTitle = event.getSource().get("ruleTitle"); String senderId = event.getSource().get("senderId"); String receiverId = event.getSource().get("receiverId"); String object = event.getSource().get("object"); - githubHooksManagement.createGamificationHistory(ruleTitle, senderId, receiverId, object); + webhookService.createGamificationHistory(ruleTitle, senderId, receiverId, object); } } diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/model/RemoteOrganization.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/model/RemoteOrganization.java new file mode 100644 index 000000000..f851f8fc5 --- /dev/null +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/model/RemoteOrganization.java @@ -0,0 +1,36 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * Copyright (C) 2020 - 2023 Meeds Association + * contact@meeds.io + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.exoplatform.gamification.github.model; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RemoteOrganization { + + long id; + + String name; + + String title; + + String description; + + String avatarUrl; +} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/model/WebHook.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/model/WebHook.java new file mode 100644 index 000000000..0833d7b15 --- /dev/null +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/model/WebHook.java @@ -0,0 +1,44 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * Copyright (C) 2020 - 2023 Meeds Association + * contact@meeds.io + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.exoplatform.gamification.github.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class WebHook implements Cloneable { + + private Long webhookId; + + private Long organizationId; + + private Boolean enabled; + + private String watchedDate; + + private String watchedBy; + + private String updatedDate; + + public WebHook clone() { // NOSONAR + return new WebHook(webhookId, organizationId, enabled, watchedDate, watchedBy, updatedDate); + } + +} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/GithubWebHookRest.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/GithubWebHookRest.java index 6ef86e2fe..167a5a069 100644 --- a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/GithubWebHookRest.java +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/GithubWebHookRest.java @@ -22,10 +22,9 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import io.meeds.gamification.service.ConnectorHookService; import io.meeds.gamification.service.ConnectorService; import org.exoplatform.container.PortalContainer; -import org.exoplatform.gamification.github.services.GithubHooksManagement; +import org.exoplatform.gamification.github.services.impl.WebhookServiceImpl; import org.exoplatform.gamification.github.utils.Utils; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; @@ -36,34 +35,29 @@ @Path("/gamification/connectors/github/") public class GithubWebHookRest implements ResourceContainer { - public static final String CONNECTOR_NAME = "github"; + public static final String CONNECTOR_NAME = "github"; - private static final String PULL_REQUEST_REVIEW_NODE_NAME = "review"; + private static final String PULL_REQUEST_REVIEW_NODE_NAME = "review"; - private static final String PULL_REQUEST_REVIEW_EVENT_NAME = "pull_request_review"; + private static final String PULL_REQUEST_REVIEW_EVENT_NAME = "pull_request_review"; - private static final String PULL_REQUEST_REVIEW_COMMENT_EVENT_NAME = "pull_request_review_comment"; + private static final String PULL_REQUEST_REVIEW_COMMENT_EVENT_NAME = "pull_request_review_comment"; - private static final String PUSH_EVENT_NAME = "push"; + private static final String PUSH_EVENT_NAME = "push"; - private static final String GITHUB_USERNAME = "login"; + private static final String GITHUB_USERNAME = "login"; - private static final String PULL_REQUEST_EVENT_NAME = "pull_request"; + private static final String PULL_REQUEST_EVENT_NAME = "pull_request"; - private static final Log LOG = ExoLogger.getLogger(GithubWebHookRest.class); + private static final Log LOG = ExoLogger.getLogger(GithubWebHookRest.class); - private final GithubHooksManagement githubHooksManagement; + private final ConnectorService connectorService; - private final ConnectorHookService connectorHookService; + private final WebhookServiceImpl webhookService; - private final ConnectorService connectorService; - - public GithubWebHookRest(GithubHooksManagement githubHooksManagement, - ConnectorHookService connectorHookService, - ConnectorService connectorService) { - this.githubHooksManagement = githubHooksManagement; - this.connectorHookService = connectorHookService; + public GithubWebHookRest(WebhookServiceImpl webhookService, ConnectorService connectorService) { this.connectorService = connectorService; + this.webhookService = webhookService; } @POST @@ -73,7 +67,7 @@ public Response githubEvent(// NOSONAR @HeaderParam("x-github-event") String event, @HeaderParam("x-hub-signature") String signature, String obj) { - if (Utils.verifySignature(connectorHookService, obj, signature)) { + if (Utils.verifySignature(webhookService, obj, signature)) { try { ObjectMapper objectMapper = new ObjectMapper(); JsonNode infoNode = objectMapper.readTree(obj); @@ -84,79 +78,77 @@ public Response githubEvent(// NOSONAR String githubId = ""; switch (event) { - case PUSH_EVENT_NAME: { - ruleTitle = "pushCode"; - githubId = infoNode.get("pusher").get("name").textValue(); - // TO DO - // to move to gamification service after the implementation of the gamification trigger service - senderId = connectorService.getAssociatedUsername(CONNECTOR_NAME, githubId); + case PUSH_EVENT_NAME: { + ruleTitle = "pushCode"; + githubId = infoNode.get("pusher").get("name").textValue(); + // TO DO + // to move to gamification service after the implementation of the gamification + // trigger service + senderId = connectorService.getAssociatedUsername(CONNECTOR_NAME, githubId); + if (senderId != null) { + Identity socialIdentity = getUserSocialId(senderId); + if (socialIdentity != null) { + receiverId = senderId; + object = infoNode.get("head_commit").get("url").textValue(); + webhookService.broadcastGithubEvent(ruleTitle, senderId, receiverId, object); + } + } + } + break; + case PULL_REQUEST_EVENT_NAME: { + githubId = infoNode.get("sender").get(GITHUB_USERNAME).textValue(); + senderId = connectorService.getAssociatedUsername(CONNECTOR_NAME, githubId); + if (infoNode.get("action").textValue().equals("opened")) { + ruleTitle = "creatPullRequest"; if (senderId != null) { Identity socialIdentity = getUserSocialId(senderId); if (socialIdentity != null) { receiverId = senderId; - object = infoNode.get("head_commit").get("url").textValue(); - githubHooksManagement.broadcastGithubEvent(ruleTitle, senderId, receiverId, object); + object = infoNode.get(PULL_REQUEST_EVENT_NAME).get("html_url").textValue(); + webhookService.broadcastGithubEvent(ruleTitle, senderId, receiverId, object); } } } - break; - case PULL_REQUEST_EVENT_NAME: { - githubId = infoNode.get("sender").get(GITHUB_USERNAME).textValue(); - senderId = connectorService.getAssociatedUsername(CONNECTOR_NAME, githubId); - if (infoNode.get("action").textValue().equals("opened")) { - ruleTitle = "creatPullRequest"; - if (senderId != null) { - Identity socialIdentity = getUserSocialId(senderId); - if (socialIdentity != null) { - receiverId = senderId; - object = infoNode.get(PULL_REQUEST_EVENT_NAME).get("html_url").textValue(); - githubHooksManagement.broadcastGithubEvent(ruleTitle, senderId, receiverId, object); - } - } - } - } - break; - case PULL_REQUEST_REVIEW_COMMENT_EVENT_NAME: { - ruleTitle = "commentPullRequest"; - githubId = infoNode.get("comment").get("user").get(GITHUB_USERNAME).textValue(); - senderId = connectorService.getAssociatedUsername(CONNECTOR_NAME, githubId); - if (senderId != null) { - Identity socialIdentity = getUserSocialId(senderId); - if (socialIdentity != null) { - receiverId = senderId; - object = infoNode.get("comment").get("_links").get("html").get("href").textValue(); - githubHooksManagement.broadcastGithubEvent(ruleTitle, senderId, receiverId, object); - } + } + break; + case PULL_REQUEST_REVIEW_COMMENT_EVENT_NAME: { + ruleTitle = "commentPullRequest"; + githubId = infoNode.get("comment").get("user").get(GITHUB_USERNAME).textValue(); + senderId = connectorService.getAssociatedUsername(CONNECTOR_NAME, githubId); + if (senderId != null) { + Identity socialIdentity = getUserSocialId(senderId); + if (socialIdentity != null) { + receiverId = senderId; + object = infoNode.get("comment").get("_links").get("html").get("href").textValue(); + webhookService.broadcastGithubEvent(ruleTitle, senderId, receiverId, object); } } - break; - case PULL_REQUEST_REVIEW_EVENT_NAME: { - ruleTitle = "reviewPullRequest"; - githubId = infoNode.get(PULL_REQUEST_REVIEW_NODE_NAME).get("user").get(GITHUB_USERNAME).textValue(); - senderId = connectorService.getAssociatedUsername(CONNECTOR_NAME, githubId); - if (senderId != null) { - Identity socialIdentity = getUserSocialId(senderId); - if (socialIdentity != null) { - receiverId = senderId; - object = infoNode.get(PULL_REQUEST_REVIEW_NODE_NAME).get("html_url").textValue(); - if (!infoNode.get(PULL_REQUEST_REVIEW_NODE_NAME).get("state").textValue().equals("commented")) { - githubHooksManagement.broadcastGithubEvent(ruleTitle, senderId, receiverId, object); - } - if (infoNode.get(PULL_REQUEST_REVIEW_NODE_NAME).get("state").textValue().equals("approved")) { - githubId = infoNode.get(PULL_REQUEST_EVENT_NAME) - .get("user") - .get(GITHUB_USERNAME) - .textValue(); - receiverId = connectorService.getAssociatedUsername(CONNECTOR_NAME, githubId); - ruleTitle = "pullRequestValidated"; - githubHooksManagement.broadcastGithubEvent(ruleTitle, receiverId, senderId, object); - } + } + break; + case PULL_REQUEST_REVIEW_EVENT_NAME: { + ruleTitle = "reviewPullRequest"; + githubId = infoNode.get(PULL_REQUEST_REVIEW_NODE_NAME).get("user").get(GITHUB_USERNAME).textValue(); + senderId = connectorService.getAssociatedUsername(CONNECTOR_NAME, githubId); + if (senderId != null) { + Identity socialIdentity = getUserSocialId(senderId); + if (socialIdentity != null) { + receiverId = senderId; + object = infoNode.get(PULL_REQUEST_REVIEW_NODE_NAME).get("html_url").textValue(); + if (!infoNode.get(PULL_REQUEST_REVIEW_NODE_NAME).get("state").textValue().equals("commented")) { + webhookService.broadcastGithubEvent(ruleTitle, senderId, receiverId, object); + } + if (infoNode.get(PULL_REQUEST_REVIEW_NODE_NAME).get("state").textValue().equals("approved")) { + githubId = infoNode.get(PULL_REQUEST_EVENT_NAME).get("user").get(GITHUB_USERNAME).textValue(); + receiverId = connectorService.getAssociatedUsername(CONNECTOR_NAME, githubId); + ruleTitle = "pullRequestValidated"; + webhookService.broadcastGithubEvent(ruleTitle, receiverId, senderId, object); } } } - break; - default: + } + break; + default: } return Response.ok().build(); diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/HooksManagementRest.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/HooksManagementRest.java index 9707f26c2..931af99ff 100644 --- a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/HooksManagementRest.java +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/HooksManagementRest.java @@ -21,45 +21,89 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import org.apache.commons.lang.StringUtils; import org.exoplatform.commons.ObjectAlreadyExistsException; import org.exoplatform.commons.exception.ObjectNotFoundException; -import org.exoplatform.gamification.github.services.GithubHooksManagement; +import org.exoplatform.gamification.github.model.WebHook; +import org.exoplatform.gamification.github.rest.builder.WebHookBuilder; +import org.exoplatform.gamification.github.rest.model.WebHookList; +import org.exoplatform.gamification.github.rest.model.WebHookRestEntity; +import org.exoplatform.gamification.github.services.WebhookService; import org.exoplatform.services.rest.resource.ResourceContainer; import org.exoplatform.services.security.ConversationState; +import java.util.Collection; +import java.util.List; + +import static io.meeds.gamification.utils.Utils.getCurrentUser; + @Path("/gamification/connectors/github/hooks") public class HooksManagementRest implements ResourceContainer { - private final GithubHooksManagement githubHooksManagement; + private final WebhookService webhookService; - public HooksManagementRest(GithubHooksManagement githubHooksManagement) { - this.githubHooksManagement = githubHooksManagement; + public HooksManagementRest(WebhookService webhookService) { + this.webhookService = webhookService; + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed("users") + @Operation(summary = "Retrieves the list GitHub webHooks", method = "GET") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Request fulfilled"), + @ApiResponse(responseCode = "401", description = "Unauthorized operation"), }) + public Response getWebHooks(@QueryParam("offset") int offset, + @Parameter(description = "Query results limit", required = true) @QueryParam("limit") int limit, + @Parameter(description = "WebHook total size") @Schema(defaultValue = "false") @QueryParam("returnSize") boolean returnSize) { + + String currentUser = getCurrentUser(); + List webHookRestEntities; + try { + WebHookList webHookList = new WebHookList(); + webHookRestEntities = getWebHookRestEntities(currentUser); + if (returnSize) { + int webHookSize = webhookService.countWebhooks(currentUser); + webHookList.setSize(webHookSize); + } + webHookList.setWebhooks(webHookRestEntities); + webHookList.setOffset(offset); + webHookList.setLimit(limit); + return Response.ok(webHookList).build(); + } catch (IllegalAccessException e) { + return Response.status(Response.Status.UNAUTHORIZED).build(); + } } @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @RolesAllowed("users") @Operation(summary = "Create a organization webhook for Remote GitHub connector.", description = "Create a organization webhook for Remote GitHub connector.", method = "POST") - @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Request fulfilled"), - @ApiResponse(responseCode = "400", description = "Invalid query input"), - @ApiResponse(responseCode = "401", description = "Unauthorized operation"), - @ApiResponse(responseCode = "500", description = "Internal server error") }) - public Response createGitHubHook(@Parameter(description = "GitHub organization name", required = true) @FormParam("hookName") String hookName, - @Parameter(description = "Hook secret", required = true) @FormParam("hookSecret") String hookSecret) { + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Request fulfilled"), + @ApiResponse(responseCode = "400", description = "Invalid query input"), + @ApiResponse(responseCode = "401", description = "Unauthorized operation"), + @ApiResponse(responseCode = "500", description = "Internal server error") }) + public Response createGitHubHook(@Parameter(description = "GitHub organization name", required = true) @FormParam("organizationName") String organizationName, + @Parameter(description = "GitHub webHook secret", required = true) @FormParam("hookSecret") String hookSecret, + @Parameter(description = "GitHub personal access token", required = true) @FormParam("accessToken") String accessToken) { - if (StringUtils.isBlank(hookName)) { - return Response.status(Response.Status.BAD_REQUEST).entity("'hookName' parameter is mandatory").build(); + if (StringUtils.isBlank(organizationName)) { + return Response.status(Response.Status.BAD_REQUEST).entity("'organizationName' parameter is mandatory").build(); + } + if (StringUtils.isBlank(accessToken)) { + return Response.status(Response.Status.BAD_REQUEST).entity("'accessToken' parameter is mandatory").build(); } if (StringUtils.isBlank(hookSecret)) { return Response.status(Response.Status.BAD_REQUEST).entity("'hookSecret' parameter is mandatory").build(); } String currentUser = ConversationState.getCurrent().getIdentity().getUserId(); try { - githubHooksManagement.addHook(hookName, hookSecret, currentUser); + webhookService.createWebhook(organizationName, hookSecret, accessToken, currentUser); return Response.status(Response.Status.CREATED).build(); } catch (IllegalAccessException e) { return Response.status(Response.Status.UNAUTHORIZED).entity(e.getMessage()).build(); @@ -69,21 +113,21 @@ public Response createGitHubHook(@Parameter(description = "GitHub organization n } @DELETE - @Path("{hookName}") + @Path("{organizationId}") @RolesAllowed("users") - @Operation(summary = "Deletes gitHub connector hook", description = "Deletes gitHub connector hook", method = "DELETE") + @Operation(summary = "Deletes gitHub organization webhook", description = "Deletes gitHub organization webhook", method = "DELETE") @ApiResponses(value = { - @ApiResponse(responseCode = "204", description = "Request fulfilled"), - @ApiResponse(responseCode = "400", description = "Bad request"), - @ApiResponse(responseCode = "401", description = "Unauthorized operation"), - @ApiResponse(responseCode = "500", description = "Internal server error"), }) - public Response deleteGitHubHook(@Parameter(description = "GitHub organization name", required = true) @PathParam("hookName") String hookName) { - if (StringUtils.isBlank(hookName)) { + @ApiResponse(responseCode = "204", description = "Request fulfilled"), + @ApiResponse(responseCode = "400", description = "Bad request"), + @ApiResponse(responseCode = "401", description = "Unauthorized operation"), + @ApiResponse(responseCode = "500", description = "Internal server error"), }) + public Response deleteGitHubHook(@Parameter(description = "GitHub organization id", required = true) @PathParam("organizationId") long organizationId) { + if (organizationId <= 0) { return Response.status(Response.Status.BAD_REQUEST).entity("'hookName' parameter is mandatory").build(); } String currentUser = ConversationState.getCurrent().getIdentity().getUserId(); try { - githubHooksManagement.deleteHook(hookName, currentUser); + webhookService.deleteConnectorHook(organizationId, currentUser); return Response.noContent().build(); } catch (IllegalAccessException e) { return Response.status(Response.Status.UNAUTHORIZED).entity(e.getMessage()).type(MediaType.TEXT_PLAIN).build(); @@ -91,4 +135,9 @@ public Response deleteGitHubHook(@Parameter(description = "GitHub organization n return Response.status(Response.Status.NOT_FOUND).entity("The GitHub hook doesn't exit").build(); } } + + private List getWebHookRestEntities(String username) throws IllegalAccessException { + Collection webHooks = webhookService.getWebhooks(username, 0, 20); + return WebHookBuilder.toRestEntities(webhookService, webHooks); + } } diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/builder/WebHookBuilder.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/builder/WebHookBuilder.java new file mode 100644 index 000000000..0d23919e3 --- /dev/null +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/builder/WebHookBuilder.java @@ -0,0 +1,65 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2023 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.exoplatform.gamification.github.rest.builder; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +import org.exoplatform.gamification.github.model.RemoteOrganization; +import org.exoplatform.gamification.github.model.WebHook; +import org.exoplatform.gamification.github.rest.model.WebHookRestEntity; +import org.exoplatform.gamification.github.services.WebhookService; +import org.exoplatform.social.core.identity.model.Identity; + +public class WebHookBuilder { + + private WebHookBuilder() { + // Class with static methods + } + + public static WebHookRestEntity toRestEntity(WebhookService webhookService, WebHook webHook) { + if (webHook == null) { + return null; + } + RemoteOrganization remoteOrganization; + Identity userIdentity = null; + try { + remoteOrganization = + webhookService.retrieveRemoteOrganization(webHook.getOrganizationId(), + webhookService.getHookAccessToken(webHook.getOrganizationId())); + } catch (IOException e) { + throw new IllegalStateException("Unable to retrieve GitHub organization info with organization id: " + + webHook.getOrganizationId()); + } + return new WebHookRestEntity(webHook.getWebhookId(), + webHook.getOrganizationId(), + webHook.getEnabled(), + webHook.getWatchedDate(), + webHook.getWatchedBy(), + webHook.getUpdatedDate(), + remoteOrganization.getName(), + remoteOrganization.getTitle(), + remoteOrganization.getDescription(), + remoteOrganization.getAvatarUrl()); + } + + public static List toRestEntities(WebhookService webhookService, Collection webHooks) { + return webHooks.stream().map(webHook -> toRestEntity(webhookService, webHook)).toList(); + } +} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/model/WebHookList.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/model/WebHookList.java new file mode 100644 index 000000000..628b4ebab --- /dev/null +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/model/WebHookList.java @@ -0,0 +1,38 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * Copyright (C) 2020 -2023 Meeds Association + * contact@meeds.io + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.exoplatform.gamification.github.rest.model; + +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@Getter +@Setter +public class WebHookList { + + private List webhooks; + + private int offset; + + private int limit; + + private int size; +} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/model/WebHookRestEntity.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/model/WebHookRestEntity.java new file mode 100644 index 000000000..bf8b36987 --- /dev/null +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/rest/model/WebHookRestEntity.java @@ -0,0 +1,53 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * Copyright (C) 2020 - 2023 Meeds Association + * contact@meeds.io + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.exoplatform.gamification.github.rest.model; + +import lombok.*; +import org.exoplatform.gamification.github.model.WebHook; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class WebHookRestEntity extends WebHook { + + private String name; + + private String title; + + private String description; + + private String avatarUrl; + + public WebHookRestEntity(long webhookId, + long organizationId, + boolean enabled, + String watchedDate, + String watchedBy, + String updatedDate, + String name, + String title, + String description, + String avatarUrl) { + super(webhookId, organizationId, enabled, watchedDate, watchedBy, updatedDate); + this.name = name; + this.title = title; + this.description = description; + this.avatarUrl = avatarUrl; + } +} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/services/GithubHooksManagement.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/services/GithubHooksManagement.java deleted file mode 100644 index 1b6cabc3c..000000000 --- a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/services/GithubHooksManagement.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * This file is part of the Meeds project (https://meeds.io/). - * Copyright (C) 2020 - 2022 Meeds Association contact@meeds.io - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.exoplatform.gamification.github.services; - -import java.io.*; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.*; - -import io.meeds.gamification.model.ConnectorHook; -import io.meeds.gamification.service.ConnectorHookService; -import io.meeds.gamification.service.ConnectorSettingService; -import io.meeds.gamification.utils.Utils; -import org.apache.commons.lang3.StringUtils; -import org.exoplatform.commons.ObjectAlreadyExistsException; -import org.exoplatform.commons.exception.ObjectNotFoundException; -import org.exoplatform.commons.file.model.FileItem; -import org.exoplatform.commons.file.services.FileService; -import org.exoplatform.commons.utils.CommonsUtils; -import org.exoplatform.social.core.manager.IdentityManager; -import org.json.JSONObject; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.exoplatform.commons.utils.IOUtil; -import org.exoplatform.services.listener.ListenerService; -import org.exoplatform.services.log.ExoLogger; -import org.exoplatform.services.log.Log; - -public class GithubHooksManagement { - - private static final Log LOG = ExoLogger.getLogger(GithubHooksManagement.class); - - private static final String GITHUB_API_URL = "https://api.github.com/orgs/"; - - private static final String[] GITHUB_EVENTS = { "push", "pull_request", "pull_request_review", - "pull_request_review_comment", "pull_request_review_comment" }; - - public static final String CONNECTOR_NAME = "github"; - - private String accessToken; - - private final ListenerService listenerService; - - private final ConnectorHookService connectorHookService; - - private final ConnectorSettingService connectorSettingService; - - private final IdentityManager identityManager; - - private final FileService fileService; - - public GithubHooksManagement(ListenerService listenerService, - ConnectorHookService connectorHookService, - ConnectorSettingService connectorSettingService, - IdentityManager identityManager, - FileService fileService) { - this.listenerService = listenerService; - this.connectorHookService = connectorHookService; - this.connectorSettingService = connectorSettingService; - this.identityManager = identityManager; - this.fileService = fileService; - } - - public void addHook(String organizationName, String secret, String currentUser) throws ObjectAlreadyExistsException, - IllegalAccessException { - - if (!Utils.isRewardingManager(currentUser)) { - throw new IllegalAccessException("The user is not authorized to create GitHub hook"); - } - if (StringUtils.isBlank(getAccessToken())) { - throw new IllegalArgumentException("Access token is required to create GitHub hook"); - } - ConnectorHook connectorHook = connectorHookService.getConnectorHook(CONNECTOR_NAME, organizationName); - if (connectorHook != null) { - throw new ObjectAlreadyExistsException(connectorHook); - } - JSONObject config = new JSONObject(); - JSONObject hook = new JSONObject(); - String url = GITHUB_API_URL + organizationName + "/hooks"; - config.put("url", CommonsUtils.getCurrentDomain() + "/portal/rest/gamification/connectors/github/webhooks"); - config.put("content_type", "json"); - config.put("insecure_ssl", "0"); - config.put("secret", secret); - hook.put("name", "web"); - hook.put("active", true); - hook.put("config", config); - hook.put("events", GITHUB_EVENTS); - try { - URL obj = new URL(url); - HttpURLConnection postConnection = (HttpURLConnection) obj.openConnection(); - postConnection.setRequestMethod("POST"); - setAuthorizationHeader(postConnection); - postConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); - postConnection.setDoOutput(true); - createGitHubWebhook(hook, postConnection); - int responseCode = postConnection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_CREATED) { // success - save(organizationName, secret, currentUser, postConnection); - } - } catch (IOException e) { - throw new IllegalStateException("Unable to open GitHub connection", e); - } - } - - private void save(String organizationName, - String secret, - String currentUser, - HttpURLConnection postConnection) throws ObjectAlreadyExistsException, IllegalAccessException { - try (InputStream in = postConnection.getInputStream()) { - String response = IOUtil.getStreamContentAsString(in); - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode infoNode = objectMapper.readTree(response); - long hookId = infoNode.get("id").longValue(); - saveWebhook(hookId, organizationName, secret, currentUser); - } catch (IOException e) { - throw new IllegalStateException("Unable to create GitHub hook", e); - } - } - - private void createGitHubWebhook(JSONObject hook, HttpURLConnection postConnection) { - try (OutputStream os = postConnection.getOutputStream()) { - os.write(hook.toString().getBytes(StandardCharsets.UTF_8)); - os.flush(); - } catch (IOException e) { - throw new IllegalStateException("Unable to create github hook", e); - } - } - - public void deleteHook(String organizationName, String currentUser) throws IllegalAccessException, ObjectNotFoundException { - if (!Utils.isRewardingManager(currentUser)) { - throw new IllegalAccessException("The user is not authorized to delete GitHub hook"); - } - ConnectorHook connectorHook = connectorHookService.getConnectorHook(CONNECTOR_NAME, organizationName); - if (connectorHook == null) { - throw new ObjectNotFoundException("Github hook with name " + organizationName + " wasn't found"); - } - String url = GITHUB_API_URL + organizationName + "/hooks/" + connectorHook.getHookRemoteId(); - URL obj; - int responseCode; - try { - obj = new URL(url); - HttpURLConnection deleteConnection = (HttpURLConnection) obj.openConnection(); - deleteConnection.setRequestMethod("DELETE"); - setAuthorizationHeader(deleteConnection); - responseCode = deleteConnection.getResponseCode(); - } catch (Exception e) { - throw new IllegalStateException("Unable to delete GitHub hook"); - } - if (responseCode == HttpURLConnection.HTTP_NO_CONTENT) { // success - deleteWebhook(organizationName, currentUser); - } else { - throw new IllegalStateException("Unable to delete GitHub hook"); - } - } - - public void createGamificationHistory(String ruleTitle, String senderId, String receiverId, String object) { - try { - Map gam = new HashMap<>(); - gam.put("ruleTitle", ruleTitle); - gam.put("senderId", senderId); - gam.put("receiverId", receiverId); - gam.put("object", object); - listenerService.broadcast("exo.gamification.generic.action", gam, ""); - LOG.info("Github action {} gamified for user {} {} {}", - ruleTitle, - senderId, - (ruleTitle.equals("pullRequestValidated")) ? "from" : "to", - receiverId); - } catch (Exception e) { - LOG.error("Cannot broadcast gamification event", e); - } - } - - public void broadcastGithubEvent(String ruleTitle, String senderId, String receiverId, String object) { - try { - Map gam = new HashMap<>(); - gam.put("ruleTitle", ruleTitle); - gam.put("senderId", senderId); - gam.put("receiverId", receiverId); - gam.put("object", object); - listenerService.broadcast("exo.github.event", gam, ""); - LOG.info("Github action {} brodcasted for user {}", ruleTitle, senderId); - } catch (Exception e) { - LOG.error("Cannot broadcast github event", e); - } - } - - private void saveWebhook(long hookId, String organizationName, String secret, String currentUser) throws IOException, - ObjectAlreadyExistsException, - IllegalAccessException { - URL obj = new URL(GITHUB_API_URL + organizationName); - HttpURLConnection connection = (HttpURLConnection) obj.openConnection(); - connection.setRequestMethod("GET"); - setAuthorizationHeader(connection); - int responseCode = connection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - try (InputStream in = connection.getInputStream()) { - String response = IOUtil.getStreamContentAsString(in); - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode infoNode = objectMapper.readTree(response); - ConnectorHook connectorHook = new ConnectorHook(); - connectorHook.setHookRemoteId(hookId); - connectorHook.setOrganizationRemoteId(infoNode.get("id").longValue()); - connectorHook.setConnectorName(CONNECTOR_NAME); - connectorHook.setName(organizationName); - connectorHook.setSecret(secret); - connectorHook.setTitle(infoNode.get("name").textValue()); - connectorHook.setDescription(infoNode.get("description").textValue()); - String userIdentityId = identityManager.getOrCreateUserIdentity(currentUser).getId(); - connectorHook.setWatchedBy(Long.parseLong(userIdentityId)); - long imageFileId = downloadAvatar(infoNode.get("avatar_url").textValue(), organizationName + "_avatar"); - connectorHook.setImageFileId(imageFileId); - connectorHookService.createConnectorHook(connectorHook, currentUser); - } catch (IOException e) { - LOG.error(e); - } - } else { - throw new IllegalArgumentException("Failed to get GitHub " + organizationName + " organization info."); - } - } - - private void deleteWebhook(String organizationName, String currentUser) throws IllegalAccessException { - connectorHookService.deleteConnectorHook(CONNECTOR_NAME, organizationName, currentUser); - } - - private long downloadAvatar(String avatarUrl, String fileName) { - try { - URL url = new URL(avatarUrl); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - int responseCode = connection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - try (BufferedInputStream in = new BufferedInputStream(connection.getInputStream()); - ByteArrayOutputStream out = new ByteArrayOutputStream()) { - int data; - while ((data = in.read()) != -1) { - out.write(data); - } - byte[] imageData = out.toByteArray(); - FileItem fileItem; - fileItem = getFileItem(fileName, imageData); - return fileItem == null || fileItem.getFileInfo() == null ? 0 : fileItem.getFileInfo().getId(); - } - } else { - throw new IllegalStateException("Error while saving connector hook image file"); - } - } catch (IOException e) { - return 0; - } - } - - private FileItem getFileItem(String fileName, byte[] imageData) { - FileItem fileItem; - try { - fileItem = new FileItem(null, - fileName, - "image/png", - "gamification", - imageData.length, - new Date(), - null, - false, - new ByteArrayInputStream(imageData)); - fileItem = fileService.writeFile(fileItem); - } catch (Exception e) { - throw new IllegalStateException("Error while saving connector hook image file"); - } - return fileItem; - } - - private String getAccessToken() { - if (accessToken == null) { - accessToken = connectorSettingService.getConnectorAccessToken(CONNECTOR_NAME); - } - return accessToken; - } - - private void setAuthorizationHeader(HttpURLConnection connection) { - connection.setRequestProperty("Authorization", "token " + getAccessToken()); - } -} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/services/WebhookService.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/services/WebhookService.java new file mode 100644 index 000000000..b15e40ff9 --- /dev/null +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/services/WebhookService.java @@ -0,0 +1,93 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * Copyright (C) 2020 - 2023 Meeds Association contact@meeds.io + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.exoplatform.gamification.github.services; + +import org.exoplatform.commons.ObjectAlreadyExistsException; +import org.exoplatform.commons.exception.ObjectNotFoundException; +import org.exoplatform.gamification.github.model.RemoteOrganization; +import org.exoplatform.gamification.github.model.WebHook; + +import java.io.IOException; +import java.util.List; + +public interface WebhookService { + + /** + * Get available github hooks using offset and limit. + * + * @param currentUser user name attempting to access connector hooks + * @param offset Offset of result + * @param limit Limit of result + * @throws IllegalAccessException when user is not authorized to access github + * hooks + * @return {@link List} of {@link WebHook} + */ + List getWebhooks(String currentUser, int offset, int limit) throws IllegalAccessException; + + /** + * Count all gitHub webhooks + * + * @param currentUser User name accessing webhooks + * @return webhooks count + * @throws IllegalAccessException when user is not authorized to get github + * webhooks + */ + int countWebhooks(String currentUser) throws IllegalAccessException; + + /** + * create github organization hook. + * + * @param organizationName github organization name + * @param secret hook secret + * @param accessToken gitHub personal access token + * @param currentUser user name attempting to create github hook + */ + void createWebhook(String organizationName, + String secret, + String accessToken, + String currentUser) throws ObjectAlreadyExistsException, IllegalAccessException; + + /** + * delete gitHub webhook + * + * @param organizationId github remote organization id + * @param currentUser user name attempting to delete gitHub hook + * @throws IllegalAccessException when user is not authorized to delete the + * gitHub hook + */ + void deleteConnectorHook(long organizationId, String currentUser) throws IllegalAccessException, ObjectNotFoundException; + + /** + * @param organizationRemoteId gitHub organization remote Id + * @return {@link String} connector hook secret + */ + String getHookSecret(long organizationRemoteId); + + /** + * @param organizationRemoteId gitHub organization remote Id + * @return {@link String} connector hook access token + */ + String getHookAccessToken(long organizationRemoteId); + + /** + * Retrieve available github organization info. + * + * @param organizationRemoteId gitHub organization remote Id + * @param accessToken gitHub personal access token + * @return {@link RemoteOrganization} + */ + RemoteOrganization retrieveRemoteOrganization(long organizationRemoteId, String accessToken) throws IOException; +} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/services/impl/WebhookServiceImpl.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/services/impl/WebhookServiceImpl.java new file mode 100644 index 000000000..1406e7fa2 --- /dev/null +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/services/impl/WebhookServiceImpl.java @@ -0,0 +1,312 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * Copyright (C) 2020 - 2023 Meeds Association contact@meeds.io + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.exoplatform.gamification.github.services.impl; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import io.meeds.gamification.utils.Utils; +import org.exoplatform.commons.ObjectAlreadyExistsException; +import org.exoplatform.commons.exception.ObjectNotFoundException; +import org.exoplatform.commons.utils.CommonsUtils; +import org.exoplatform.gamification.github.model.WebHook; +import org.exoplatform.gamification.github.model.RemoteOrganization; +import org.exoplatform.gamification.github.services.WebhookService; +import org.exoplatform.gamification.github.storage.WebHookStorage; +import org.exoplatform.social.core.manager.IdentityManager; +import org.exoplatform.web.security.codec.CodecInitializer; +import org.exoplatform.web.security.security.TokenServiceInitializationException; +import org.json.JSONObject; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.exoplatform.commons.utils.IOUtil; +import org.exoplatform.services.listener.ListenerService; +import org.exoplatform.services.log.ExoLogger; +import org.exoplatform.services.log.Log; + +public class WebhookServiceImpl implements WebhookService { + + private static final Log LOG = ExoLogger.getLogger(WebhookServiceImpl.class); + + private static final String GITHUB_API_URL = "https://api.github.com/orgs/"; + + private static final String[] GITHUB_EVENTS = { "push", "pull_request", "pull_request_review", "pull_request_review_comment", + "pull_request_review_comment" }; + + private final ListenerService listenerService; + + private final IdentityManager identityManager; + + private final WebHookStorage webHookStorage; + + public WebhookServiceImpl(ListenerService listenerService, IdentityManager identityManager, WebHookStorage webHookStorage) { + this.listenerService = listenerService; + this.identityManager = identityManager; + this.webHookStorage = webHookStorage; + } + + public List getWebhooks(String currentUser, int offset, int limit) throws IllegalAccessException { + if (!Utils.isRewardingManager(currentUser)) { + throw new IllegalAccessException("The user is not authorized to access gitHub Hooks"); + } + List hooksIds = webHookStorage.getWebhookIds(offset, limit); + return hooksIds.stream().map(webHookStorage::getWebHookById).toList(); + } + + public int countWebhooks(String currentUser) throws IllegalAccessException { + if (!Utils.isRewardingManager(currentUser)) { + throw new IllegalAccessException("The user is not authorized to access gitHub Hooks"); + } + return webHookStorage.countWebhooks(); + } + + public void createWebhook(String organizationName, + String secret, + String accessToken, + String currentUser) throws ObjectAlreadyExistsException, IllegalAccessException { + + if (!Utils.isRewardingManager(currentUser)) { + throw new IllegalAccessException("The user is not authorized to create GitHub hook"); + } + RemoteOrganization remoteOrganization; + try { + remoteOrganization = retrieveRemoteOrganization(organizationName, accessToken); + } catch (IOException e) { + throw new IllegalStateException("Unable to retrieve GitHub organization info.", e); + } + WebHook webHook = webHookStorage.getWebhookByOrganizationId(remoteOrganization.getId()); + if (webHook != null) { + throw new ObjectAlreadyExistsException(webHook); + } + JSONObject config = new JSONObject(); + JSONObject hook = new JSONObject(); + String url = GITHUB_API_URL + organizationName + "/hooks"; + config.put("url", + "https://fac5-2a01-cb05-890f-e600-20b2-cbf1-33ab-2540.ngrok-free.app" + + "/portal/rest/gamification/connectors/github/webhooks"); + config.put("content_type", "json"); + config.put("insecure_ssl", "0"); + config.put("secret", secret); + hook.put("name", "web"); + hook.put("active", true); + hook.put("config", config); + hook.put("events", GITHUB_EVENTS); + try { + URL obj = new URL(url); + HttpURLConnection postConnection = (HttpURLConnection) obj.openConnection(); + postConnection.setRequestMethod("POST"); + setAuthorizationHeader(postConnection, accessToken); + postConnection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + postConnection.setDoOutput(true); + createGitHubWebhook(hook, postConnection); + int responseCode = postConnection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_CREATED) { // success + saveWebhook(remoteOrganization, secret, accessToken, currentUser, postConnection); + } + } catch (IOException e) { + throw new IllegalStateException("Unable to open GitHub connection", e); + } + } + + private void saveWebhook(RemoteOrganization remoteOrganization, + String secret, + String token, + String currentUser, + HttpURLConnection postConnection) throws ObjectAlreadyExistsException, IllegalAccessException { + try (InputStream in = postConnection.getInputStream()) { + String response = IOUtil.getStreamContentAsString(in); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode infoNode = objectMapper.readTree(response); + long hookId = infoNode.get("id").longValue(); + if (!Utils.isRewardingManager(currentUser)) { + throw new IllegalAccessException("The user is not authorized to create GitHub hook"); + } + WebHook webHook = new WebHook(); + webHook.setWebhookId(hookId); + webHook.setOrganizationId(remoteOrganization.getId()); + webHook.setWatchedBy(currentUser); + secret = encode(secret); + token = encode(token); + webHookStorage.saveWebHook(webHook, secret, token); + } catch (IOException e) { + throw new IllegalStateException("Unable to create GitHub hook", e); + } + } + + private void createGitHubWebhook(JSONObject hook, HttpURLConnection postConnection) { + try (OutputStream os = postConnection.getOutputStream()) { + os.write(hook.toString().getBytes(StandardCharsets.UTF_8)); + os.flush(); + } catch (IOException e) { + throw new IllegalStateException("Unable to create github hook", e); + } + } + + public void deleteConnectorHook(long organizationId, String currentUser) throws IllegalAccessException, + ObjectNotFoundException { + if (!Utils.isRewardingManager(currentUser)) { + throw new IllegalAccessException("The user is not authorized to delete GitHub hook"); + } + WebHook webHook = webHookStorage.getWebhookByOrganizationId(organizationId); + if (webHook == null) { + throw new ObjectNotFoundException("Github hook for organization id : " + organizationId + " wasn't found"); + } + String url = GITHUB_API_URL + organizationId + "/hooks/" + webHook.getWebhookId(); + URL obj; + int responseCode; + try { + obj = new URL(url); + HttpURLConnection deleteConnection = (HttpURLConnection) obj.openConnection(); + deleteConnection.setRequestMethod("DELETE"); + setAuthorizationHeader(deleteConnection, getHookAccessToken(organizationId)); + responseCode = deleteConnection.getResponseCode(); + } catch (Exception e) { + throw new IllegalStateException("Unable to delete GitHub hook"); + } + if (responseCode == HttpURLConnection.HTTP_NO_CONTENT) { // success + deleteWebhook(organizationId); + } else { + throw new IllegalStateException("Unable to delete GitHub hook"); + } + } + + @Override + public String getHookSecret(long organizationRemoteId) { + return decode(webHookStorage.getWebHookHookSecret(organizationRemoteId)); + } + + @Override + public String getHookAccessToken(long organizationRemoteId) { + return decode(webHookStorage.getWebHookAccessToken(organizationRemoteId)); + } + + public void createGamificationHistory(String ruleTitle, String senderId, String receiverId, String object) { + try { + Map gam = new HashMap<>(); + gam.put("ruleTitle", ruleTitle); + gam.put("senderId", senderId); + gam.put("receiverId", receiverId); + gam.put("object", object); + listenerService.broadcast("exo.gamification.generic.action", gam, ""); + LOG.info("Github action {} gamified for user {} {} {}", + ruleTitle, + senderId, + (ruleTitle.equals("pullRequestValidated")) ? "from" : "to", + receiverId); + } catch (Exception e) { + LOG.error("Cannot broadcast gamification event", e); + } + } + + public void broadcastGithubEvent(String ruleTitle, String senderId, String receiverId, String object) { + try { + Map gam = new HashMap<>(); + gam.put("ruleTitle", ruleTitle); + gam.put("senderId", senderId); + gam.put("receiverId", receiverId); + gam.put("object", object); + listenerService.broadcast("exo.github.event", gam, ""); + LOG.info("Github action {} brodcasted for user {}", ruleTitle, senderId); + } catch (Exception e) { + LOG.error("Cannot broadcast github event", e); + } + } + + private void deleteWebhook(long organizationId) { + webHookStorage.deleteWebHook(organizationId); + } + + private void setAuthorizationHeader(HttpURLConnection connection, String accessToken) { + connection.setRequestProperty("Authorization", "token " + accessToken); + } + + private RemoteOrganization retrieveRemoteOrganization(String organizationName, String accessToken) throws IOException { + URL obj = new URL(GITHUB_API_URL + organizationName); + HttpURLConnection connection = (HttpURLConnection) obj.openConnection(); + connection.setRequestMethod("GET"); + setAuthorizationHeader(connection, accessToken); + int responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + try (InputStream in = connection.getInputStream()) { + String response = IOUtil.getStreamContentAsString(in); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode infoNode = objectMapper.readTree(response); + RemoteOrganization gitHubOrganization = new RemoteOrganization(); + gitHubOrganization.setId(infoNode.get("id").longValue()); + gitHubOrganization.setName(organizationName); + gitHubOrganization.setTitle(infoNode.get("name").textValue()); + gitHubOrganization.setDescription(infoNode.get("description").textValue()); + gitHubOrganization.setAvatarUrl(infoNode.get("avatar_url").textValue()); + return gitHubOrganization; + } catch (IOException e) { + throw new IllegalStateException("Unable to retrieve GitHub organization: " + organizationName); + } + } else { + throw new IllegalStateException("Unable to retrieve GitHub organization: " + organizationName); + } + } + + public RemoteOrganization retrieveRemoteOrganization(long organizationId, String accessToken) throws IOException { + URL obj = new URL(GITHUB_API_URL + organizationId); + HttpURLConnection connection = (HttpURLConnection) obj.openConnection(); + connection.setRequestMethod("GET"); + setAuthorizationHeader(connection, accessToken); + int responseCode = connection.getResponseCode(); + if (responseCode == HttpURLConnection.HTTP_OK) { + try (InputStream in = connection.getInputStream()) { + String response = IOUtil.getStreamContentAsString(in); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode infoNode = objectMapper.readTree(response); + RemoteOrganization gitHubOrganization = new RemoteOrganization(); + gitHubOrganization.setId(infoNode.get("id").longValue()); + gitHubOrganization.setName(infoNode.get("login").textValue()); + gitHubOrganization.setTitle(infoNode.get("name").textValue()); + gitHubOrganization.setDescription(infoNode.get("description").textValue()); + gitHubOrganization.setAvatarUrl(infoNode.get("avatar_url").textValue()); + return gitHubOrganization; + } catch (IOException e) { + throw new IllegalStateException("Unable to retrieve GitHub organization with id: " + organizationId); + } + } else { + throw new IllegalStateException("Unable to retrieve GitHub organization with id: " + organizationId); + } + } + + private static String encode(String token) { + try { + CodecInitializer codecInitializer = CommonsUtils.getService(CodecInitializer.class); + return codecInitializer.getCodec().encode(token); + } catch (TokenServiceInitializationException e) { + LOG.warn("Error when encoding token", e); + return null; + } + } + + private static String decode(String encryptedToken) { + try { + CodecInitializer codecInitializer = CommonsUtils.getService(CodecInitializer.class); + return codecInitializer.getCodec().decode(encryptedToken); + } catch (TokenServiceInitializationException e) { + LOG.warn("Error when decoding token", e); + return null; + } + } +} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/storage/WebHookStorage.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/storage/WebHookStorage.java new file mode 100644 index 000000000..541e901bb --- /dev/null +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/storage/WebHookStorage.java @@ -0,0 +1,87 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2023 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.exoplatform.gamification.github.storage; + +import java.util.Date; +import java.util.List; + +import org.exoplatform.commons.ObjectAlreadyExistsException; +import org.exoplatform.gamification.github.dao.WebHookDAO; +import org.exoplatform.gamification.github.entity.WebhookEntity; +import org.exoplatform.gamification.github.model.WebHook; + +import static org.exoplatform.gamification.github.storage.mapper.WebHookMapper.fromEntity; +import static org.exoplatform.gamification.github.storage.mapper.WebHookMapper.toEntity; + +public class WebHookStorage { + + private final WebHookDAO webHookDAO; + + public WebHookStorage(WebHookDAO gitHubHookDAO) { + this.webHookDAO = gitHubHookDAO; + } + + public WebHook saveWebHook(WebHook webHook, String secret, String accessToken) throws ObjectAlreadyExistsException { + WebHook existsWebHook = getWebhookByOrganizationId(webHook.getOrganizationId()); + if (existsWebHook == null) { + WebhookEntity webhookEntity = toEntity(webHook); + webhookEntity.setWatchedDate(new Date()); + webhookEntity.setUpdatedDate(new Date()); + webhookEntity.setSecret(secret); + webhookEntity.setToken(accessToken); + webhookEntity.setEnabled(true); + webhookEntity = webHookDAO.create(webhookEntity); + return fromEntity(webhookEntity); + } else { + throw new ObjectAlreadyExistsException(existsWebHook); + } + } + + public WebHook getWebHookById(Long id) { + return fromEntity(webHookDAO.find(id)); + } + + public List getWebhookIds(int offset, int limit) { + return webHookDAO.getWebhookIds(offset, limit); + } + + public int countWebhooks() { + return webHookDAO.count().intValue(); + } + + public WebHook getWebhookByOrganizationId(long organizationId) { + WebhookEntity connectorHookEntity = webHookDAO.getWebhookByOrganizationId(organizationId); + return fromEntity(connectorHookEntity); + } + + public WebHook deleteWebHook(long organizationId) { + WebhookEntity webhookEntity = webHookDAO.getWebhookByOrganizationId(organizationId); + if (webhookEntity != null) { + webHookDAO.delete(webhookEntity); + } + return fromEntity(webhookEntity); + } + + public String getWebHookHookSecret(long organizationId) { + return webHookDAO.getWebHookHookSecret(organizationId); + } + + public String getWebHookAccessToken(long organizationId) { + return webHookDAO.getWebHookAccessToken(organizationId); + } +} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/storage/mapper/WebHookMapper.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/storage/mapper/WebHookMapper.java new file mode 100644 index 000000000..026119cbd --- /dev/null +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/storage/mapper/WebHookMapper.java @@ -0,0 +1,64 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * Copyright (C) 2020 - 2023 Meeds Association + * contact@meeds.io + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.exoplatform.gamification.github.storage.mapper; + +import io.meeds.gamification.utils.Utils; +import org.exoplatform.commons.utils.CommonsUtils; +import org.exoplatform.gamification.github.entity.WebhookEntity; +import org.exoplatform.gamification.github.model.WebHook; +import org.exoplatform.social.core.manager.IdentityManager; + +public class WebHookMapper { + + private WebHookMapper() { + // Class with static methods + } + + public static WebhookEntity toEntity(WebHook webHook) { + if (webHook == null) { + return null; + } + WebhookEntity webhookEntity = new WebhookEntity(); + if (webHook.getOrganizationId() > 0) { + webhookEntity.setOrganizationId(webHook.getOrganizationId()); + } + if (webHook.getWebhookId() > 0) { + webhookEntity.setWebhookId(webHook.getWebhookId()); + } + if (webHook.getWatchedBy() != null) { + IdentityManager identityManager = CommonsUtils.getService(IdentityManager.class); + String userIdentityId = identityManager.getOrCreateUserIdentity(webHook.getWatchedBy()).getId(); + webhookEntity.setWatchedBy(Long.parseLong(userIdentityId)); + } + return webhookEntity; + } + + public static WebHook fromEntity(WebhookEntity webhookEntity) { + if (webhookEntity == null) { + return null; + } + IdentityManager identityManager = CommonsUtils.getService(IdentityManager.class); + String watchedBy = identityManager.getIdentity(String.valueOf(webhookEntity.getWatchedBy())).getRemoteId(); + return new WebHook(webhookEntity.getWebhookId(), + webhookEntity.getOrganizationId(), + webhookEntity.getEnabled(), + webhookEntity.getWatchedDate() != null ? Utils.toSimpleDateFormat(webhookEntity.getWatchedDate()) : null, + watchedBy, + webhookEntity.getUpdatedDate() != null ? Utils.toSimpleDateFormat(webhookEntity.getUpdatedDate()) : null); + } + +} diff --git a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/utils/Utils.java b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/utils/Utils.java index 6fcd4d9fb..e01a89e57 100644 --- a/gamification-github-services/src/main/java/org/exoplatform/gamification/github/utils/Utils.java +++ b/gamification-github-services/src/main/java/org/exoplatform/gamification/github/utils/Utils.java @@ -21,8 +21,7 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; -import io.meeds.gamification.model.ConnectorHook; -import io.meeds.gamification.service.ConnectorHookService; +import org.exoplatform.gamification.github.services.WebhookService; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.json.JSONObject; @@ -40,7 +39,7 @@ private Utils() { // Private constructor for Utils class } - public static boolean verifySignature(ConnectorHookService connectorHookService, String payload, String signature) { + public static boolean verifySignature(WebhookService webhookService, String payload, String signature) { JSONObject jsonPayload = new JSONObject(payload); // Extract the "organization" field @@ -48,13 +47,12 @@ public static boolean verifySignature(ConnectorHookService connectorHookService, // Extract the organization ID long organizationId = organization.getLong("id"); - String secret = connectorHookService.getConnectorHookSecret("github", organizationId); + String secret = webhookService.getHookSecret(organizationId); boolean isValid = false; if (signature == null || secret == null) { return false; } - try { Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), HMAC_SHA1_ALGORITHM); @@ -76,5 +74,4 @@ public static boolean verifySignature(ConnectorHookService connectorHookService, } return isValid; } - } diff --git a/gamification-github-services/src/main/resources/conf/portal/configuration.xml b/gamification-github-services/src/main/resources/conf/portal/configuration.xml index db496254f..f2acab23f 100644 --- a/gamification-github-services/src/main/resources/conf/portal/configuration.xml +++ b/gamification-github-services/src/main/resources/conf/portal/configuration.xml @@ -28,10 +28,6 @@ org.exoplatform.gamification.github.rest.HooksManagementRest - - org.exoplatform.gamification.github.services.GithubHooksManagement - - org.exoplatform.services.listener.ListenerService diff --git a/gamification-github-services/src/main/resources/conf/portal/gamification-github-storage-configuration.xml b/gamification-github-services/src/main/resources/conf/portal/gamification-github-storage-configuration.xml index 666f7749e..1251785ab 100644 --- a/gamification-github-services/src/main/resources/conf/portal/gamification-github-storage-configuration.xml +++ b/gamification-github-services/src/main/resources/conf/portal/gamification-github-storage-configuration.xml @@ -21,7 +21,21 @@ xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> - org.exoplatform.gamification.github.dao.GitHubHookDAO + org.exoplatform.gamification.github.dao.WebHookDAO + + + + org.exoplatform.gamification.github.storage.WebHookStorage + + + + org.exoplatform.gamification.github.services.WebhookService + org.exoplatform.gamification.github.services.impl.WebhookServiceImpl + + + + org.exoplatform.gamification.github.services.WebhookService + org.exoplatform.gamification.github.services.impl.WebhookServiceImpl diff --git a/gamification-github-services/src/main/resources/db/changelog/github-connector.db.changelog-1.0.0.xml b/gamification-github-services/src/main/resources/db/changelog/github-connector.db.changelog-1.0.0.xml index a4451f29f..020376f8d 100644 --- a/gamification-github-services/src/main/resources/db/changelog/github-connector.db.changelog-1.0.0.xml +++ b/gamification-github-services/src/main/resources/db/changelog/github-connector.db.changelog-1.0.0.xml @@ -91,6 +91,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gamification-github-services/src/test/java/org/exoplatform/gamification/github/BaseGithubConnectorsTest.java b/gamification-github-services/src/test/java/org/exoplatform/gamification/github/BaseGithubConnectorsTest.java deleted file mode 100644 index 5734dd64d..000000000 --- a/gamification-github-services/src/test/java/org/exoplatform/gamification/github/BaseGithubConnectorsTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.exoplatform.gamification.github; - -import static org.junit.Assert.*; - -import java.io.Serializable; -import java.util.*; - -import org.junit.*; - -import org.exoplatform.container.PortalContainer; -import org.exoplatform.container.component.RequestLifeCycle; -import org.exoplatform.gamification.github.dao.GitHubHookDAO; -import org.exoplatform.gamification.github.entity.GitHubHookEntity; - -public abstract class BaseGithubConnectorsTest { - - protected static PortalContainer container; - - protected List entitiesToClean = new ArrayList<>(); - - @BeforeClass - public static void beforeTest() { - container = PortalContainer.getInstance(); - assertNotNull(container); - assertTrue(container.isStarted()); - } - - @Before - public void beforeMethodTest() { - RequestLifeCycle.begin(container); - } - - @After - public void afterMethodTest() { - GitHubHookDAO gitHubHookDAO = getService(GitHubHookDAO.class); - RequestLifeCycle.end(); - RequestLifeCycle.begin(container); - if (!entitiesToClean.isEmpty()) { - for (Serializable entity : entitiesToClean) { - if (entity instanceof GitHubHookEntity) { - gitHubHookDAO.delete((GitHubHookEntity) entity); - } else { - throw new IllegalStateException("Entity not managed" + entity); - } - } - } - - int hooksCount = gitHubHookDAO.findAll().size(); - assertEquals("The previous test didn't cleaned hooks entities correctly, should add entities to clean into 'entitiesToClean' list.", - 0, - hooksCount); - - RequestLifeCycle.end(); - } - - protected T getService(Class componentType) { - return container.getComponentInstanceOfType(componentType); - } - - protected GitHubHookEntity newGitHubHookEntity(long id, - String organization, - String repo, - String webhook, - String events, - String exoEnvironment, - boolean enabled) { - GitHubHookDAO gitHubHookDAO = getService(GitHubHookDAO.class); - GitHubHookEntity gitHubHookEntity = new GitHubHookEntity(); - gitHubHookEntity.setGithubId(id); - gitHubHookEntity.setOrganization(organization); - gitHubHookEntity.setRepo(repo); - gitHubHookEntity.setWebhook(webhook); - gitHubHookEntity.setEnabled(enabled); - gitHubHookEntity.setExoEnvironment(exoEnvironment); - gitHubHookEntity.setEvents(events); - gitHubHookEntity.setCreatedDate(new Date()); - gitHubHookEntity.setUpdatedDate(new Date()); - gitHubHookEntity = gitHubHookDAO.create(gitHubHookEntity); - entitiesToClean.add(gitHubHookEntity); - return gitHubHookEntity; - } - -} diff --git a/gamification-github-services/src/test/resources/conf/portal/configuration.xml b/gamification-github-services/src/test/resources/conf/portal/configuration.xml index e6cd2a0a4..b18fef3a2 100644 --- a/gamification-github-services/src/test/resources/conf/portal/configuration.xml +++ b/gamification-github-services/src/test/resources/conf/portal/configuration.xml @@ -21,7 +21,8 @@ xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> - org.exoplatform.gamification.github.services.GithubHooksManagement + org.exoplatform.gamification.github.services.WebhookService + org.exoplatform.gamification.github.services.impl.WebhookServiceImpl diff --git a/gamification-github-webapp/src/main/resources/locale/portlet/GitHubWebHookManagement_en.properties b/gamification-github-webapp/src/main/resources/locale/portlet/GitHubWebHookManagement_en.properties index b11c5a240..7a3ba681f 100644 --- a/gamification-github-webapp/src/main/resources/locale/portlet/GitHubWebHookManagement_en.properties +++ b/gamification-github-webapp/src/main/resources/locale/portlet/GitHubWebHookManagement_en.properties @@ -28,12 +28,21 @@ githubConnector.admin.label.instructions.stepOne=1.Ask your GitHub Project admin githubConnector.admin.label.instructions.stepTwo=2.Once done, fill these fields below with the generated API Key, Secret Key, and Redirect URL defined in the OAuth application githubConnector.admin.label.addOrganization=Add a GitHub Organization +githubConnector.admin.label.editOrganization=Edit GitHub Organization githubConnector.admin.label.enableWatch=Enable to watch events from a GitHub organization. githubConnector.admin.label.organization=GitHub Organization githubConnector.admin.label.organization.placeholder=Enter the GitHub organization name githubConnector.admin.label.hookSecret=Webhook secret githubConnector.admin.label.hookSecret.placeholder=Enter the GitHub Webhook secret -githubConnector.admin.label.AccessToken=Access token -githubConnector.admin.label.AccessToken.placeholder=Enter the GitHub Webhook secret -githubConnector.admin.label.AccessToken.title.add=Add GitHub access token -githubConnector.admin.label.AccessToken.title.edit=Edit GitHub access token +githubConnector.admin.label.accessToken=Personal access token +githubConnector.admin.label.accessToken.placeholder=Enter the GitHub personal access token +githubConnector.admin.label.Webhook=Webhook + +githubConnector.webhook.form.label.button.next=Next +githubConnector.webhook.form.label.button.back=Back +githubConnector.webhook.label.watchedBy=Project watched from {0} by +githubConnector.webhook.label.watchProject=Watch project +githubConnector.webhook.label.loadMore=Load More +githubConnector.webhook.title.confirmDeleteProject=Delete github project? +githubConnector.webhook.message.confirmDeleteConnectorHook=Are you sure you want to delete this github project? + diff --git a/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminConnectorHook.vue b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminConnectorHook.vue new file mode 100644 index 000000000..c47c51f6d --- /dev/null +++ b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminConnectorHook.vue @@ -0,0 +1,135 @@ + + + + \ No newline at end of file diff --git a/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminConnectorHookList.vue b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminConnectorHookList.vue new file mode 100644 index 000000000..6e76c0ddd --- /dev/null +++ b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminConnectorHookList.vue @@ -0,0 +1,106 @@ + + + + \ No newline at end of file diff --git a/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminConnectorItem.vue b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminConnectorItem.vue index 228b7a1ae..aac82428e 100644 --- a/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminConnectorItem.vue +++ b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminConnectorItem.vue @@ -138,8 +138,8 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - + + + right + @opened="stepper = 1" + @closed="clear"> @@ -79,23 +156,56 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. \ No newline at end of file diff --git a/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminHookSettingDrawer.vue b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminHookSettingDrawer.vue deleted file mode 100644 index 3bfdd05da..000000000 --- a/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/components/GithubAdminHookSettingDrawer.vue +++ /dev/null @@ -1,103 +0,0 @@ - - - - \ No newline at end of file diff --git a/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/extension.js b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/extension.js index fee5a06e3..6dfe4c277 100644 --- a/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/extension.js +++ b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/extension.js @@ -15,6 +15,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import './initComponents'; +import './services.js'; export function init() { extensionRegistry.registerComponent('gamification-admin-connector', 'admin-connector-item', { diff --git a/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/initComponents.js b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/initComponents.js index 9959272db..d06426d17 100644 --- a/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/initComponents.js +++ b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/initComponents.js @@ -15,13 +15,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import GithubAdminConnectorItem from './components/GithubAdminConnectorItem.vue'; -import GithubAdminHookSettingDrawer from './components/GithubAdminHookSettingDrawer.vue'; import GithubAdminHookFormDrawer from './components/GithubAdminHookFormDrawer.vue'; +import GithubAdminConnectorHookList from './components/GithubAdminConnectorHookList.vue'; +import GithubAdminConnectorHook from './components/GithubAdminConnectorHook.vue'; const components = { 'github-admin-connector-item': GithubAdminConnectorItem, - 'github-admin-hook-setting-drawer': GithubAdminHookSettingDrawer, 'github-admin-hook-form-drawer': GithubAdminHookFormDrawer, + 'github-admin-connector-hook-list': GithubAdminConnectorHookList, + 'github-admin-connector-hook': GithubAdminConnectorHook, }; for (const key in components) { diff --git a/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/js/GithubConnectorService.js b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/js/GithubConnectorService.js new file mode 100644 index 000000000..70589b70d --- /dev/null +++ b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/js/GithubConnectorService.js @@ -0,0 +1,60 @@ +/* + * This file is part of the Meeds project (https://meeds.io/). + * Copyright (C) 2020 - 2023 Meeds Association + * contact@meeds.io + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +export function getGithubWebHooks(offset, limit) { + return fetch(`${eXo.env.portal.context}/${eXo.env.portal.rest}/gamification/connectors/github/hooks?offset=${offset || 0}&limit=${limit|| 10}&returnSize=true`, { + method: 'GET', + credentials: 'include', + }).then((resp) => { + if (resp?.ok) { + return resp.json(); + } else { + throw new Error('Error when getting github webhooks'); + } + }); +} + +export function saveGithubWebHook(organizationName, accessToken , hookSecret) { + const formData = new FormData(); + formData.append('organizationName', organizationName); + formData.append('accessToken', accessToken); + formData.append('hookSecret', hookSecret); + + return fetch(`${eXo.env.portal.context}/${eXo.env.portal.rest}/gamification/connectors/github/hooks`, { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams(formData).toString(), + }).then(resp => { + if (!resp?.ok) { + throw new Error('Error when saving github webhook'); + } + }); +} + +export function deleteGithubWebHook(organizationId) { + return fetch(`${eXo.env.portal.context}/${eXo.env.portal.rest}/gamification/connectors/github/hooks/${organizationId}`, { + method: 'DELETE', + credentials: 'include', + }).then(resp => { + if (!resp?.ok) { + throw new Error('Error when deleting github webhook'); + } + }); +} \ No newline at end of file diff --git a/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/services.js b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/services.js new file mode 100644 index 000000000..561f5cf9b --- /dev/null +++ b/gamification-github-webapp/src/main/webapp/vue-app/githubAdminConnectorExtension/services.js @@ -0,0 +1,27 @@ +/* + * + * This file is part of the Meeds project (https://meeds.io/). + * + * Copyright (C) 2020 - 2023 Meeds Association contact@meeds.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +import * as githubConnectorService from './js/GithubConnectorService.js'; + +if (!Vue.prototype.$githubConnectorService) { + window.Object.defineProperty(Vue.prototype, '$githubConnectorService', { + value: githubConnectorService, + }); +} \ No newline at end of file