diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84fdeb8d67b..37127c27116 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v3 with: - node-version: 16.18.1 + node-version: 16.20.2 cache: yarn cache-dependency-path: | colab-webapp/yarn.lock diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 16ddc28d39a..78651d70fc2 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -36,7 +36,7 @@ jobs: - name: Set up Node uses: actions/setup-node@v3 with: - node-version: 16.18.1 + node-version: 16.20.2 cache: yarn cache-dependency-path: | colab-webapp/yarn.lock diff --git a/client-generator-plugin/src/main/resources/templates/tsconfig.json b/client-generator-plugin/src/main/resources/templates/tsconfig.json index 6477e420a70..378397e1d87 100644 --- a/client-generator-plugin/src/main/resources/templates/tsconfig.json +++ b/client-generator-plugin/src/main/resources/templates/tsconfig.json @@ -8,7 +8,6 @@ "strict": true, "moduleResolution": "node", "allowSyntheticDefaultImports": true, - "suppressImplicitAnyIndexErrors": true, "declaration": true }, "files": [ diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/controller/CronTab.java b/colab-api/src/main/java/ch/colabproject/colab/api/controller/CronTab.java index 5abcee5bdec..619fdee1bbc 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/controller/CronTab.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/controller/CronTab.java @@ -7,6 +7,8 @@ package ch.colabproject.colab.api.controller; import ch.colabproject.colab.api.controller.document.ExternalDataManager; +import ch.colabproject.colab.api.controller.monitoring.CronJobLogManager; +import ch.colabproject.colab.api.model.monitoring.CronJobLogName; import ch.colabproject.colab.api.security.SessionManager; import javax.ejb.Schedule; import javax.ejb.Singleton; @@ -35,13 +37,18 @@ public class CronTab { @Inject private ExternalDataManager externalDataManager; + /** To manage CronJobLogs */ + @Inject + private CronJobLogManager cronJobLogManager; + /** - * Each minutes + * Each minute */ @Schedule(hour = "*", minute = "*") public void saveActivityDates() { logger.trace("CRON: Persist activity dates to database"); sessionManager.writeActivityDatesToDatabase(); + cronJobLogManager.updateCronJobLogLastRunTime(CronJobLogName.SAVE_ACTIVITIES_DATE); } /** @@ -51,6 +58,7 @@ public void saveActivityDates() { public void dropOldHttpSession() { logger.info("CRON: drop expired http session"); sessionManager.clearExpiredHttpSessions(); + cronJobLogManager.updateCronJobLogLastRunTime(CronJobLogName.DROP_OLD_HTTP_SESSIONS); } /** @@ -60,5 +68,6 @@ public void dropOldHttpSession() { public void dropOldUrlMetadata() { logger.info("CRON: clean url metadata cache"); externalDataManager.clearOutdated(); + cronJobLogManager.updateCronJobLogLastRunTime(CronJobLogName.DROP_OLD_URL_METADATA); } } diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/controller/EntityGatheringBagForPropagation.java b/colab-api/src/main/java/ch/colabproject/colab/api/controller/EntityGatheringBagForPropagation.java index b08c3b301ea..61dc625f651 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/controller/EntityGatheringBagForPropagation.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/controller/EntityGatheringBagForPropagation.java @@ -8,21 +8,23 @@ import ch.colabproject.colab.api.model.WithWebsocketChannels; import ch.colabproject.colab.api.persistence.jpa.card.CardTypeDao; +import ch.colabproject.colab.api.persistence.jpa.project.ProjectDao; import ch.colabproject.colab.api.persistence.jpa.team.TeamMemberDao; import ch.colabproject.colab.api.persistence.jpa.user.UserDao; import ch.colabproject.colab.api.ws.WebsocketMessagePreparer; import ch.colabproject.colab.api.ws.message.IndexEntry; import ch.colabproject.colab.api.ws.message.PrecomputedWsMessages; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.transaction.Status; import java.io.Serializable; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; -import javax.enterprise.context.RequestScoped; -import javax.inject.Inject; -import javax.transaction.Status; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Transaction sidekick used to collect updated and deleted entities. Once the transaction is @@ -69,6 +71,12 @@ public class EntityGatheringBagForPropagation implements Serializable { @Inject private CardTypeDao cardTypeDao; + /** + * To resolve nested channels + */ + @Inject + private ProjectDao projectDao; + /** * TO sudo */ @@ -205,7 +213,7 @@ private void precomputeMessage() { this.precomputed = true; requestManager.sudo(() -> { return this.message = WebsocketMessagePreparer.prepareWsMessage(userDao, teamDao, - cardTypeDao, filtered, deleted); + cardTypeDao, projectDao, filtered, deleted); }); logger.debug("Precomputed: {}", message); } catch (Exception ex) { diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/controller/WebsocketManager.java b/colab-api/src/main/java/ch/colabproject/colab/api/controller/WebsocketManager.java index 09c19139946..fa77ec85078 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/controller/WebsocketManager.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/controller/WebsocketManager.java @@ -14,6 +14,7 @@ import ch.colabproject.colab.api.model.user.HttpSession; import ch.colabproject.colab.api.model.user.User; import ch.colabproject.colab.api.persistence.jpa.card.CardTypeDao; +import ch.colabproject.colab.api.persistence.jpa.project.ProjectDao; import ch.colabproject.colab.api.persistence.jpa.team.TeamMemberDao; import ch.colabproject.colab.api.persistence.jpa.user.UserDao; import ch.colabproject.colab.api.presence.PresenceManager; @@ -157,6 +158,12 @@ public class WebsocketManager { @Inject private CardTypeDao cardTypeDao; + /** + * Project persistence handler + */ + @Inject + private ProjectDao projectDao; + /** * Presence Manager */ @@ -479,8 +486,7 @@ public void processSubscription( /** * Add the given session to the set identified by the given channel, in the given map. * - * @param map map which contains sets - * @param keyId set id + * @param channel websocket channel to which we subscribe * @param session session to remove from the set */ private void subscribe(WebsocketChannel channel, Session session) { @@ -676,6 +682,7 @@ private void propagateSignOut(HttpSession httpSession) { userDao, teamMemberDao, cardTypeDao, + projectDao, httpSession.getChannelsBuilder(), new WsSignOutMessage(httpSession)); this.propagate(prepareWsMessage); diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/controller/config/ConfigurationManager.java b/colab-api/src/main/java/ch/colabproject/colab/api/controller/config/ConfigurationManager.java index 67bddc754af..ceb5e768c9a 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/controller/config/ConfigurationManager.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/controller/config/ConfigurationManager.java @@ -30,6 +30,7 @@ public ColabConfig getConfig() { config .setDisplayCreateLocalAccountButton(ColabConfiguration.getDisplayLocalAccountButton()); config.setYjsApiEndpoint(ColabConfiguration.getYjsUrlWs()); + config.setJcrRepositoryFileSizeLimit(ColabConfiguration.getJcrRepositoryFileSizeLimit()); return config; } diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/controller/monitoring/CronJobLogManager.java b/colab-api/src/main/java/ch/colabproject/colab/api/controller/monitoring/CronJobLogManager.java new file mode 100644 index 00000000000..14bb96ae832 --- /dev/null +++ b/colab-api/src/main/java/ch/colabproject/colab/api/controller/monitoring/CronJobLogManager.java @@ -0,0 +1,76 @@ +/* + * The coLAB project + * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO + * + * Licensed under the MIT License + */ +package ch.colabproject.colab.api.controller.monitoring; + +import ch.colabproject.colab.api.model.monitoring.CronJobLog; +import ch.colabproject.colab.api.model.monitoring.CronJobLogName; +import ch.colabproject.colab.api.persistence.jpa.monitoring.CronJobLogDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ejb.LocalBean; +import javax.ejb.Stateless; +import javax.ejb.TransactionAttribute; +import javax.ejb.TransactionAttributeType; +import javax.inject.Inject; +import java.time.OffsetDateTime; + +/** + * Logic to manage cron job logging + * + * @author mikkelvestergaard + */ +@Stateless +@LocalBean +public class CronJobLogManager { + + /** + * logger + */ + private static final Logger logger = LoggerFactory.getLogger(CronJobLogManager.class); + + /** + * CronJobLog persistence + */ + @Inject + private CronJobLogDao cronJobLogDao; + + /** + * Create a new cronJobLog with given cronJobLogName + * + * @param jobLogName name of the cronJobLog to create + * + * @return created cronJobLog + */ + private CronJobLog createCronJobLog(CronJobLogName jobLogName) { + CronJobLog cronJobLog = new CronJobLog(); + cronJobLog.setJobName(jobLogName); + cronJobLogDao.persistCronJobLog(cronJobLog); + + return cronJobLog; + } + + /** + * Update a cronJobLog's lastRunTime + * + * @param jobName name of cronJob to update + */ + @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) + public void updateCronJobLogLastRunTime(CronJobLogName jobName) { + logger.debug("Update cronJobLog lastRunTime {}", jobName); + + CronJobLog cronJobLog = cronJobLogDao.findCronJobLogByName(jobName); + + if (cronJobLog == null) { + cronJobLog = createCronJobLog(jobName); + } + + OffsetDateTime now = OffsetDateTime.now(); + cronJobLog.setLastRunTime(now); + + } +} diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/controller/team/InstanceMakerManager.java b/colab-api/src/main/java/ch/colabproject/colab/api/controller/team/InstanceMakerManager.java index 12ab76e5e9b..0b06b815b7b 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/controller/team/InstanceMakerManager.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/controller/team/InstanceMakerManager.java @@ -87,7 +87,7 @@ public List getInstanceMakersForProject(Long projectId) { Project project = projectManager.assertAndGetProject(projectId); logger.debug("Get instanceMakers: {}", project); - return instanceMakerDao.findInstanceMakersByProject(project); + return project.getInstanceMakers(); } /** @@ -148,8 +148,11 @@ public InstanceMaker addAndPersistInstanceMaker(Project model, User user) { public InstanceMaker addInstanceMaker(Project model, User user) { logger.debug("Add instance maker to user {} for model {}", user, model); - if (model != null && user != null - && findInstanceMakerByProjectAndUser(model, user) != null) { + if (model == null) { + throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE); + } + + if (user != null && findInstanceMakerByProjectAndUser(model, user) != null) { throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE); } @@ -157,6 +160,7 @@ && findInstanceMakerByProjectAndUser(model, user) != null) { instanceMaker.setUser(user); instanceMaker.setProject(model); + model.getInstanceMakers().add(instanceMaker); return instanceMaker; } diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/controller/user/UserManager.java b/colab-api/src/main/java/ch/colabproject/colab/api/controller/user/UserManager.java index 5025f6aff64..5cdd57575df 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/controller/user/UserManager.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/controller/user/UserManager.java @@ -9,6 +9,7 @@ import ch.colabproject.colab.api.Helper; import ch.colabproject.colab.api.controller.RequestManager; import ch.colabproject.colab.api.controller.ValidationManager; +import ch.colabproject.colab.api.controller.team.InstanceMakerManager; import ch.colabproject.colab.api.controller.team.TeamManager; import ch.colabproject.colab.api.controller.token.TokenManager; import ch.colabproject.colab.api.exceptions.ColabMergeException; @@ -36,6 +37,8 @@ import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.inject.Inject; + +import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,6 +95,10 @@ public class UserManager { @Inject private TeamManager teamManager; + /** InstanceMaker specific logic management */ + @Inject + private InstanceMakerManager instanceMakerManager; + /** Account persistence handling */ @Inject private AccountDao accountDao; @@ -149,7 +156,14 @@ public User getUserById(Long id) { public List getUsersForProject(Long projectId) { logger.debug("Get users of project #{}", projectId); - return teamManager.getUsersForProject(projectId); + List teamMembers = teamManager.getUsersForProject(projectId); + List instanceMakers = instanceMakerManager.getUsersForProject(projectId); + + List allUsers = Lists.newArrayList(); + allUsers.addAll(teamMembers); + allUsers.addAll(instanceMakers); + + return allUsers; } /** diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/model/monitoring/CronJobLog.java b/colab-api/src/main/java/ch/colabproject/colab/api/model/monitoring/CronJobLog.java new file mode 100644 index 00000000000..dfb4acf7636 --- /dev/null +++ b/colab-api/src/main/java/ch/colabproject/colab/api/model/monitoring/CronJobLog.java @@ -0,0 +1,131 @@ +/* + * The coLAB project + * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO + * + * Licensed under the MIT License + */ +package ch.colabproject.colab.api.model.monitoring; + +import ch.colabproject.colab.api.model.tools.EntityHelper; +import ch.colabproject.colab.generator.model.interfaces.WithId; +import ch.colabproject.colab.generator.model.interfaces.WithJsonDiscriminator; +import ch.colabproject.colab.generator.model.tools.DateSerDe; + +import javax.json.bind.annotation.JsonbTypeDeserializer; +import javax.json.bind.annotation.JsonbTypeSerializer; +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import java.time.OffsetDateTime; + +/** + * Store cronjob related information + * + * @author mikkelvestergaard + */ +@Entity +@Table +@NamedQuery(name = "CronJobLog.findAll", query = "SELECT c from CronJobLog c") +@NamedQuery(name = "CronJobLog.findByName", + query = "SELECT c from CronJobLog c where c.jobName = :jobName") +public class CronJobLog implements WithJsonDiscriminator, WithId { + + private static final long serialVersionUID = 1L; + + /** cron job log sequence name */ + public static final String CRONJOBLOG_SEQUENCE_NAME = "monitoring_seq"; + + /** + * Unique id + */ + @Id + @SequenceGenerator(name = CRONJOBLOG_SEQUENCE_NAME, allocationSize = 1) + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = CRONJOBLOG_SEQUENCE_NAME) + private Long id; + + /** + * Name of cronJob + */ + @NotNull + @Enumerated(EnumType.STRING) + private CronJobLogName jobName; + + /** + * persisted cronJob time + */ + @JsonbTypeDeserializer(DateSerDe.class) + @JsonbTypeSerializer(DateSerDe.class) + private OffsetDateTime lastRunTime; + + // --------------------------------------------------------------------------------------------- + // getters and setters + // --------------------------------------------------------------------------------------------- + + /** + * @return the project ID + */ + @Override + public Long getId() { + return id; + } + + /** + * Set id + * + * @param id id + */ + public void setId(Long id) { + this.id = id; + } + + /** + * @return the cronJob name + */ + public CronJobLogName getJobName() { + return jobName; + } + + /** + * Set cronJob name + * + * @param jobName name + */ + public void setJobName(CronJobLogName jobName) { + this.jobName = jobName; + } + + /** + * @return cronjob time + */ + public OffsetDateTime getLastRunTime() { + return lastRunTime; + } + + /** + * @param lastRunTime new cronJobTime + */ + public void setLastRunTime(OffsetDateTime lastRunTime) { + this.lastRunTime = lastRunTime; + } + + // --------------------------------------------------------------------------------------------- + // concerning the whole class + // --------------------------------------------------------------------------------------------- + + @Override + public int hashCode() { + return EntityHelper.hashCode(this); + } + + @Override + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + public boolean equals(Object obj) { + return EntityHelper.equals(this, obj); + } + + @Override + public String toString() { + return "CronJobLog{" + "id=" + id + ", jobName=" + getJobName() + + ", lastRunTime=" + getLastRunTime() + '}'; + } + +} diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/model/monitoring/CronJobLogName.java b/colab-api/src/main/java/ch/colabproject/colab/api/model/monitoring/CronJobLogName.java new file mode 100644 index 00000000000..6c1d8669971 --- /dev/null +++ b/colab-api/src/main/java/ch/colabproject/colab/api/model/monitoring/CronJobLogName.java @@ -0,0 +1,31 @@ +/* + * The coLAB project + * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO + * + * Licensed under the MIT License + */ +package ch.colabproject.colab.api.model.monitoring; + +/** + * A cron job log can have one of these names + * + * @author mikkelvestergaard + */ +public enum CronJobLogName { + /** + * Persist activity dates to database + */ + SAVE_ACTIVITIES_DATE, + /** + * Drop expired http session + */ + DROP_OLD_HTTP_SESSIONS, + /** + * Clean url metadata cache + */ + DROP_OLD_URL_METADATA, + /** + * Database backup + */ + DATABASE_BACKUP, +} diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/model/project/InstanceMaker.java b/colab-api/src/main/java/ch/colabproject/colab/api/model/project/InstanceMaker.java index 35a0cc01845..9cd366bd935 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/model/project/InstanceMaker.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/model/project/InstanceMaker.java @@ -47,7 +47,6 @@ @Index(columnList = "user_id"), } ) -@NamedQuery(name = "InstanceMaker.findAll", query = "SELECT im from InstanceMaker im") @NamedQuery( name = "InstanceMaker.findByProjectAndUser", query = "SELECT i from InstanceMaker i " @@ -57,10 +56,7 @@ // name = "InstanceMaker.findByUser", // query = "SELECT i FROM InstanceMaker i " // + "WHERE i.user IS NOT NULL AND i.user.id = :userId") -@NamedQuery( - name = "InstanceMaker.findByProject", - query = "SELECT i FROM InstanceMaker i " - + "WHERE i.project.id = :projectId") + public class InstanceMaker implements ColabEntity, WithWebsocketChannels { private static final long serialVersionUID = 1L; diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/model/project/Project.java b/colab-api/src/main/java/ch/colabproject/colab/api/model/project/Project.java index 722cdb1f4bc..c1200c42f72 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/model/project/Project.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/model/project/Project.java @@ -411,6 +411,13 @@ public boolean isOrWasGlobal() { return this.isGlobalProject() || this.initialGlobal; } + /** + * + * @return is project a model + */ + @JsonbTransient + public boolean isModel() { return this.type == ProjectType.MODEL; } + // --------------------------------------------------------------------------------------------- // concerning the whole class // --------------------------------------------------------------------------------------------- diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/model/token/InvitationToken.java b/colab-api/src/main/java/ch/colabproject/colab/api/model/token/InvitationToken.java index ef3cbe7877b..ac19d708206 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/model/token/InvitationToken.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/model/token/InvitationToken.java @@ -143,7 +143,7 @@ public String getRedirectTo() { // as it will lead to an access denied exception Project project = getProject(); if (project != null && project.getId() != null) { - return "/editor/" + project.getId(); + return "/new-project-access/" + project.getId(); } } return ""; diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/model/token/ModelSharingToken.java b/colab-api/src/main/java/ch/colabproject/colab/api/model/token/ModelSharingToken.java index e3fd6d5ca05..21d4549457c 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/model/token/ModelSharingToken.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/model/token/ModelSharingToken.java @@ -132,7 +132,7 @@ public String getRedirectTo() { Project project = getProject(); if (project != null && project.getId() != null) { - return "/newModelShared/" + project.getId(); + return "/new-model-shared/" + project.getId(); } } diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/model/token/ResetLocalAccountPasswordToken.java b/colab-api/src/main/java/ch/colabproject/colab/api/model/token/ResetLocalAccountPasswordToken.java index 9770795ff58..0069f1133f3 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/model/token/ResetLocalAccountPasswordToken.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/model/token/ResetLocalAccountPasswordToken.java @@ -81,7 +81,7 @@ public void setLocalAccount(LocalAccount localAccount) { @Override public String getRedirectTo() { if (localAccount != null) { - return "/settings/user"; // "/settings/account/" + localAccount.getId(); + return "/go-to-profile"; } else { return ""; } diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/model/user/User.java b/colab-api/src/main/java/ch/colabproject/colab/api/model/user/User.java index 1fc6a51af11..91835613404 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/model/user/User.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/model/user/User.java @@ -93,7 +93,7 @@ public class User implements ColabEntity, WithWebsocketChannels { private OffsetDateTime activityDate = null; /** - * persisted terms and data policy agreement time + * persisted terms of use and data policy agreement time */ @JsonbTypeDeserializer(DateSerDe.class) @JsonbTypeSerializer(DateSerDe.class) diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/persistence/jpa/monitoring/CronJobLogDao.java b/colab-api/src/main/java/ch/colabproject/colab/api/persistence/jpa/monitoring/CronJobLogDao.java new file mode 100644 index 00000000000..e36c2df0ab2 --- /dev/null +++ b/colab-api/src/main/java/ch/colabproject/colab/api/persistence/jpa/monitoring/CronJobLogDao.java @@ -0,0 +1,98 @@ +/* + * The coLAB project + * Copyright (C) 2022-2023 AlbaSim, MEI, HEIG-VD, HES-SO + * + * Licensed under the MIT License + */ +package ch.colabproject.colab.api.persistence.jpa.monitoring; + +import ch.colabproject.colab.api.model.monitoring.CronJobLog; +import ch.colabproject.colab.api.model.monitoring.CronJobLogName; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.ejb.LocalBean; +import javax.ejb.Stateless; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.PersistenceContext; +import javax.persistence.TypedQuery; +import java.util.List; + +/** + * Cronjob logs persistence + * + * @author mikkelvestergaard + */ +@Stateless +@LocalBean +public class CronJobLogDao { + + /** + * logger + */ + private static final Logger logger = LoggerFactory.getLogger(CronJobLogDao.class); + + /** + * Access to the persistence unit + */ + @PersistenceContext(unitName = "COLAB_PU") + private EntityManager em; + +// /** +// * Find a cronJobLog by id +// * +// * @param id of the cronJobLog to fetch +// * +// * @return the cronJobLog with the given id or null if such a cronJobLog does not exist +// */ +// public CronJobLog findCronJobLog(Long id) { +// logger.trace("find cronJobLog #{}", id); +// +// return em.find(CronJobLog.class, id); +// } + + /** + * Get all cronJobLogs + * + * @return list of all cronJobLogs + */ + public List findAllCronJobLogs() { + logger.trace("find all cronJobLogs"); + + TypedQuery query = em.createNamedQuery("CronJobLog.findAll", CronJobLog.class); + + return query.getResultList(); + } + + /** + * Find a cronJobLog by name + * + * @param jobName the name of the cronJobLog + * + * @return the cronJobLog or null if not found + */ + public CronJobLog findCronJobLogByName(CronJobLogName jobName) { + logger.trace("find cronJobLog {}", jobName); + + try { + return em.createNamedQuery("CronJobLog.findByName", CronJobLog.class) + .setParameter("jobName", jobName) + .getSingleResult(); + } catch (NoResultException ex) { + return null; + } + } + + /** + * Persist the cron job log to the database + * + * @param cronJobLog the cron job log to persist + */ + public void persistCronJobLog(CronJobLog cronJobLog) { + logger.trace("persist cronJobLog {}", cronJobLog); + + em.persist(cronJobLog); + } + +} diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/persistence/jpa/project/InstanceMakerDao.java b/colab-api/src/main/java/ch/colabproject/colab/api/persistence/jpa/project/InstanceMakerDao.java index 3834c76f9fd..3ffee4dc23e 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/persistence/jpa/project/InstanceMakerDao.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/persistence/jpa/project/InstanceMakerDao.java @@ -10,7 +10,9 @@ import ch.colabproject.colab.api.model.project.Project; import ch.colabproject.colab.api.model.user.User; -import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.ejb.LocalBean; import javax.ejb.Stateless; import javax.persistence.EntityManager; @@ -18,9 +20,6 @@ import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Instance maker persistence *

@@ -55,19 +54,6 @@ public InstanceMaker findInstanceMaker(Long id) { return em.find(InstanceMaker.class, id); } - /** - * Get all instanceMakers - * - * @return list of all instanceMakers - */ - public List findAllInstanceMakers() { - logger.trace("find all instanceMakers"); - - TypedQuery query = em.createNamedQuery("InstanceMaker.findAll", InstanceMaker.class); - - return query.getResultList(); - } - /** * Find the instance maker who match the given project and the given user. * @@ -106,21 +92,6 @@ public InstanceMaker findInstanceMakerByProjectAndUser(Project project, User use // return query.getResultList(); // } - /** - * Find the instance makers related to the given project - * - * @param project the project - * @return the matching instance makers - */ - public List findInstanceMakersByProject(Project project) { - TypedQuery query = em.createNamedQuery("InstanceMaker.findByProject", - InstanceMaker.class); - - query.setParameter("projectId", project.getId()); - - return query.getResultList(); - } - // /** // * Update instance maker. Only fields which are editable by users will be impacted. // * diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/rest/config/bean/ColabConfig.java b/colab-api/src/main/java/ch/colabproject/colab/api/rest/config/bean/ColabConfig.java index b1bf131015a..dd57be4d8fd 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/rest/config/bean/ColabConfig.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/rest/config/bean/ColabConfig.java @@ -7,6 +7,7 @@ package ch.colabproject.colab.api.rest.config.bean; import ch.colabproject.colab.generator.model.annotations.ExtractJavaDoc; + import javax.validation.constraints.NotNull; /** @@ -18,17 +19,23 @@ public class ColabConfig { /** - * Indicated whether or not the "create an account" button should be displayed + * Indicated whether the "create an account" button should be displayed */ @NotNull private boolean displayCreateLocalAccountButton; - + /** - * + * The URI to access the MongoDB container with WS protocol. Used for lexical */ @NotNull private String yjsApiEndpoint; + /** + * The per file maximum size expressed in bytes + */ + @NotNull + private Long jcrRepositoryFileSizeLimit; + /** * Get the value of yjsApiEndpoint * @@ -66,4 +73,22 @@ public void setDisplayCreateLocalAccountButton(boolean displayCreateLocalAccount this.displayCreateLocalAccountButton = displayCreateLocalAccountButton; } + /** + * Get the value of getJcrRepositoryFileSizeLimit + * + * @return the value of getJcrRepositoryFileSizeLimit + */ + public Long getJcrRepositoryFileSizeLimit() { + return jcrRepositoryFileSizeLimit; + } + + /** + * Set the value of jcrRepositoryFileSizeLimit + * + * @param jcrRepositoryFileSizeLimit the value of jcrRepositoryFileSizeLimit + */ + public void setJcrRepositoryFileSizeLimit(Long jcrRepositoryFileSizeLimit) { + this.jcrRepositoryFileSizeLimit = jcrRepositoryFileSizeLimit; + } + } diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/rest/monitoring/CronJobLogRestEndpoint.java b/colab-api/src/main/java/ch/colabproject/colab/api/rest/monitoring/CronJobLogRestEndpoint.java new file mode 100644 index 00000000000..a5b65c4fdb1 --- /dev/null +++ b/colab-api/src/main/java/ch/colabproject/colab/api/rest/monitoring/CronJobLogRestEndpoint.java @@ -0,0 +1,57 @@ +/* + * The coLAB project + * Copyright (C) 2022-2023 AlbaSim, MEI, HEIG-VD, HES-SO + * + * Licensed under the MIT License + */ +package ch.colabproject.colab.api.rest.monitoring; + +import ch.colabproject.colab.api.model.monitoring.CronJobLog; +import ch.colabproject.colab.api.persistence.jpa.monitoring.CronJobLogDao; +import ch.colabproject.colab.generator.model.annotations.AdminResource; +import ch.colabproject.colab.generator.model.annotations.AuthenticationRequired; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import java.util.List; + +/** + * REST CronJobLog controller + * + * @author mikkelvestergaard + */ +@Path("cronJobLogs") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +@AuthenticationRequired +public class CronJobLogRestEndpoint { + + /** + * logger + */ + private static final Logger logger = LoggerFactory.getLogger(CronJobLogRestEndpoint.class); + + /** + * CronJobLog persistence handler + */ + @Inject + private CronJobLogDao cronJobLogDao; + + /** + * Get all cron job logs + * + * @return list of all cron job logs + */ + @GET + @AdminResource + public List getAllCronJobLogs() { + logger.debug("get all cron job logs"); + return cronJobLogDao.findAllCronJobLogs(); + } +} diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/rest/security/SecurityRestEndPoint.java b/colab-api/src/main/java/ch/colabproject/colab/api/rest/security/SecurityRestEndPoint.java index 33890f58e66..7804fff995a 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/rest/security/SecurityRestEndPoint.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/rest/security/SecurityRestEndPoint.java @@ -6,7 +6,7 @@ */ package ch.colabproject.colab.api.rest.security; -import ch.colabproject.colab.api.security.TosAndDataPolicyManager; +import ch.colabproject.colab.api.security.TermsOfUseManager; import javax.inject.Inject; import javax.ws.rs.Consumes; @@ -16,7 +16,7 @@ import javax.ws.rs.core.MediaType; /** - * REST SecurityRestEndpoint for ToS and Data Policy + * REST SecurityRestEndpoint for Terms of Use and Data Policy * * @author mikkelvestergaard */ @@ -26,17 +26,17 @@ public class SecurityRestEndPoint { /** - * To get TosAndDataPolicy timestamp + * To get the last timestamp when the Terms of Use and Data Policy were updated */ @Inject - private TosAndDataPolicyManager tosAndDataPolicyManager; + private TermsOfUseManager termsOfUseManager; /** - * Get the current TosAndDataPolicy as unix timestamp + * Get the last timestamp when the Terms of Use and Data Policy were updated as a unix timestamp * - * @return current TosAndDataPolicy timestamp + * @return Current Terms Of Use timestamp */ @GET - @Path("getTosAndDataPolicyTimeEpoch") - public Long getTosAndDataPolicyTimeEpoch() { return tosAndDataPolicyManager.getEpochTime(); } + @Path("getTermsOfUseTimeEpoch") + public Long getTermsOfUseTimeEpoch() { return termsOfUseManager.getEpochTime(); } } diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/rest/team/InstanceMakerRestEndpoint.java b/colab-api/src/main/java/ch/colabproject/colab/api/rest/team/InstanceMakerRestEndpoint.java index 0124f2a5c25..f5c68ad291c 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/rest/team/InstanceMakerRestEndpoint.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/rest/team/InstanceMakerRestEndpoint.java @@ -1,3 +1,9 @@ +/* + * The coLAB project + * Copyright (C) 2022-2023 AlbaSim, MEI, HEIG-VD, HES-SO + * + * Licensed under the MIT License + */ package ch.colabproject.colab.api.rest.team; import ch.colabproject.colab.api.controller.team.InstanceMakerManager; @@ -44,18 +50,6 @@ public class InstanceMakerRestEndpoint { // InstanceMakers // ********************************************************************************************* -// /** -// * Get all instanceMakers -// * -// * @return list of all instanceMakers -// */ -// @GET -// @AdminResource -// public List getAllInstanceMakers() { -// logger.debug("get all instanceMakers"); -// return instanceMakerDao.findAllInstanceMakers(); -// } - /** * Get the instanceMakers of the project * diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/security/AuthenticationFilter.java b/colab-api/src/main/java/ch/colabproject/colab/api/security/AuthenticationFilter.java index a91a34dc823..95849a1690a 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/security/AuthenticationFilter.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/security/AuthenticationFilter.java @@ -12,12 +12,9 @@ import ch.colabproject.colab.generator.model.annotations.AuthenticationRequired; import ch.colabproject.colab.generator.model.annotations.ConsentNotRequired; import ch.colabproject.colab.generator.model.exceptions.HttpErrorMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; import javax.annotation.Priority; import javax.inject.Inject; import javax.ws.rs.container.ContainerRequestContext; @@ -26,9 +23,11 @@ import javax.ws.rs.core.Context; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; /** * Intercept all request to the API and check user has required permission. @@ -72,10 +71,10 @@ public class AuthenticationFilter implements ContainerRequestFilter { private SessionManager sessionManager; /** - * To get TosAndDataPolicy timestamp + * To get the last timestamp when the Terms of Use and Data Policy were updated */ @Inject - private TosAndDataPolicyManager tosAndDataPolicyManager; + private TermsOfUseManager termsOfUseManager; /** * Get all method or class annotations matching the given type. @@ -134,9 +133,10 @@ public void filter(ContainerRequestContext requestContext) throws IOException { ConsentNotRequired.class, targetClass, targetMethod); - if (consentAnnotations.isEmpty() && (currentUser.getAgreedTime() == null || currentUser.getAgreedTime().isBefore(tosAndDataPolicyManager.getTimestamp()))) { - // current user is authenticated but need to accept new TosAndDataPolicy - logger.trace("Request aborted:user has not agreed to new TosAndDataPolicy"); + if (consentAnnotations.isEmpty() && (currentUser.getAgreedTime() == null + || currentUser.getAgreedTime().isBefore(termsOfUseManager.getTimestamp()))) { + // current user is authenticated but need to accept new TermsOfUse + logger.trace("Request aborted:user has not agreed to new TermsOfUse"); abortWith = HttpErrorMessage.forbidden(); } } diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/security/TermsOfUseManager.java b/colab-api/src/main/java/ch/colabproject/colab/api/security/TermsOfUseManager.java new file mode 100644 index 00000000000..83d00c89273 --- /dev/null +++ b/colab-api/src/main/java/ch/colabproject/colab/api/security/TermsOfUseManager.java @@ -0,0 +1,47 @@ +/* + * The coLAB project + * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO + * + * Licensed under the MIT License + */ +package ch.colabproject.colab.api.security; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; + +import static ch.colabproject.colab.api.setup.ColabConfiguration.getTermsOfUseDate; + +/** + * To store the last date when the Terms of Use and Data Policy were updated + * + * @author mikkelvestergaard + */ +public class TermsOfUseManager { + /** + * Epoch time of the most recent Terms of Use and Data Policy update + */ + private static final Long EPOCH_TIME = getTermsOfUseDate(); + + /** + * Date of the most recent Terms of Use and Data Policy update + */ + private static final OffsetDateTime TIMESTAMP = + OffsetDateTime.ofInstant(Instant.ofEpochMilli(EPOCH_TIME), ZoneId.systemDefault()); + + /** + * Get Terms of Use and Data Policy timestamp as Epoch Time + * + * @return the timestamp + */ + public Long getEpochTime() { return EPOCH_TIME; } + + /** + * Get Terms of Use and Data Policy timestamp as OffsetDateTime + * + * @return the timestamp + */ + public OffsetDateTime getTimestamp() { + return TIMESTAMP; + } +} diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/security/TosAndDataPolicyManager.java b/colab-api/src/main/java/ch/colabproject/colab/api/security/TosAndDataPolicyManager.java deleted file mode 100644 index 8c93b465ef5..00000000000 --- a/colab-api/src/main/java/ch/colabproject/colab/api/security/TosAndDataPolicyManager.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * The coLAB project - * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO - * - * Licensed under the MIT License - */ -package ch.colabproject.colab.api.security; - -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneId; - -/** - * To store the Terms of Service and Data Policy dates - * - * @author mikkelvestergaard - */ -public class TosAndDataPolicyManager { - - - /** - * Epoch time of the most recent ToS and Data Policy update - */ - private static final Long EPOCHTIME = 1700780400L; - - /** - * Date of the most recent ToS and Data Policy update - */ - private static final OffsetDateTime TIMESTAMP = OffsetDateTime.ofInstant(Instant.ofEpochMilli(EPOCHTIME), ZoneId.systemDefault()); - - /** - * Get ToS and Data Policy timestamp as Epoch Time - * - * @return the timestamp - */ - public Long getEpochTime() { return EPOCHTIME; } - - /** - * Get ToS and Data Policy timestamp as OffsetDateTime - * - * @return the timestamp - */ - public OffsetDateTime getTimestamp() { - return TIMESTAMP; - } -} diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/setup/ColabConfiguration.java b/colab-api/src/main/java/ch/colabproject/colab/api/setup/ColabConfiguration.java index 19508c45985..c19f178da0a 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/setup/ColabConfiguration.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/setup/ColabConfiguration.java @@ -6,6 +6,10 @@ */ package ch.colabproject.colab.api.setup; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + /** * Some configuration parameters * @@ -140,6 +144,17 @@ public class ColabConfiguration { */ public static final String LOCAL_ACCOUNT_BUTTON_VALUE = "true"; + /** + * Date when the terms of use and data policy was last changed in yyyy-MM-dd format + */ + public static final String TERMS_OF_USE_DATE = "colab.termsofuse.date"; + + /** + * Default date when the terms of use and data policy was last changed in EpochTime (2023-11-24T00:00:00) + * milliseconds + */ + public static final Long TERMS_OF_USE_DATE_DEFAULT_IN_MS = 1700780400000L; + /** * Maximum file upload size */ @@ -213,7 +228,7 @@ public static String getBuildNumber() { * Get name of the running docker image * * @return the running docker images or empty if running app is not a docker - * container + * container */ public static String getBuildImages() { return System.getProperty(BUILD_IMAGES_PROPERTY, DEFAULT_BUILD_IMAGES_VALUE); @@ -312,6 +327,26 @@ public static boolean getDisplayLocalAccountButton() { LOCAL_ACCOUNT_BUTTON_VALUE).equals("true"); } + /** + * @return The current terms of use and data policy date in Epochtime milliseconds + */ + public static Long getTermsOfUseDate() { + String value = System.getProperty(TERMS_OF_USE_DATE); + + if (value == null) { + return TERMS_OF_USE_DATE_DEFAULT_IN_MS; + } + + try { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + formatter.setLenient(false); + Date date = formatter.parse(value); + return date.getTime(); + } catch (ParseException pe) { + return TERMS_OF_USE_DATE_DEFAULT_IN_MS; + } + } + /** * @return The per file maximum size expressed in bytes */ @@ -327,14 +362,14 @@ public static Long getJcrRepositoryFileSizeLimit() { */ public static Long getJcrRepositoryProjectQuota() { var value = System.getProperty(JCR_REPOSITORY_PROJECT_QUOTA_MB, - JCR_REPOSITORY_MAX_FILE_SIZE_MB_DEFAULT); - var parsed = tryParsePositive(value, JCR_REPOSITORY_MAX_FILE_SIZE_MB_DEFAULT); + JCR_REPOSITORY_PROJECT_QUOTA_MB_DEFAULT); + var parsed = tryParsePositive(value, JCR_REPOSITORY_PROJECT_QUOTA_MB_DEFAULT); return parsed << 20;// convert to bytes } /** * @return The URI to access the MongoDB container. Used for file persistence - * with JCR + * with JCR */ public static String getJcrMongoDbUri() { return System.getProperty(JCR_MONGO_DB_URI, JCR_MONGO_DB_URI_DEFAULT); @@ -342,8 +377,8 @@ public static String getJcrMongoDbUri() { /** * @return The URI to access the MongoDB container with WS protocol. Used for - * lexical data - * persistence + * lexical data + * persistence */ public static String getYjsUrlWs() { return System.getProperty(YJS_URL_WS, YJS_URL_WS_DEFAULT); @@ -351,8 +386,8 @@ public static String getYjsUrlWs() { /** * @return The URI to access the MongoDB container with HTTP protocol. Used for - * lexical data - * persistence + * lexical data + * persistence */ public static String getYjsInternalUrl() { return System.getProperty(YJS_INTERNAL_URL, YJS_INTERNAL_URL_DEFAULT); @@ -363,7 +398,6 @@ public static String getYjsInternalUrl() { * * @param value * @param dflt fallback value, used in case parsing fails or value is negative - * * @return The parsed value or the default value */ private static Long tryParsePositive(String value, String dflt) { diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/ws/WebsocketMessagePreparer.java b/colab-api/src/main/java/ch/colabproject/colab/api/ws/WebsocketMessagePreparer.java index 204c2923a48..da4d3921a2b 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/ws/WebsocketMessagePreparer.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/ws/WebsocketMessagePreparer.java @@ -8,6 +8,7 @@ import ch.colabproject.colab.api.model.WithWebsocketChannels; import ch.colabproject.colab.api.persistence.jpa.card.CardTypeDao; +import ch.colabproject.colab.api.persistence.jpa.project.ProjectDao; import ch.colabproject.colab.api.persistence.jpa.team.TeamMemberDao; import ch.colabproject.colab.api.persistence.jpa.user.UserDao; import ch.colabproject.colab.api.ws.channel.model.WebsocketChannel; @@ -17,16 +18,12 @@ import ch.colabproject.colab.api.ws.message.PrecomputedWsMessages; import ch.colabproject.colab.api.ws.message.WsMessage; import ch.colabproject.colab.api.ws.message.WsUpdateMessage; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import javax.websocket.EncodeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.websocket.EncodeException; +import java.util.*; + /** * Some convenient methods to help sending data through websockets. * @@ -127,6 +124,7 @@ private static void addAsDeleted(Map> byChanne * @param userDao provide userDao to resolve nested channels * @param teamDao provide teamDao to resolve nested channels * @param cardTypeDao provide cardTypeDao to resolve nested channels + * @param projectDao provide projectDao to resolve nested channels * @param updated set of created/updated entities * @param deleted set of just destroyed-entities index entry * @@ -138,6 +136,7 @@ public static PrecomputedWsMessages prepareWsMessage( UserDao userDao, TeamMemberDao teamDao, CardTypeDao cardTypeDao, + ProjectDao projectDao, Set updated, Set deleted ) throws EncodeException { @@ -146,7 +145,7 @@ public static PrecomputedWsMessages prepareWsMessage( updated.forEach(object -> { logger.trace("Process updated entity {}", object); - object.getChannelsBuilder().computeChannels(userDao, teamDao, cardTypeDao) + object.getChannelsBuilder().computeChannels(userDao, teamDao, cardTypeDao, projectDao) .forEach(channel -> { addAsUpdated(messagesByChannel, channel, object); }); @@ -154,7 +153,7 @@ public static PrecomputedWsMessages prepareWsMessage( deleted.forEach(object -> { logger.trace("Process deleted entry {}", object); - object.getChannelsBuilder().computeChannels(userDao, teamDao, cardTypeDao) + object.getChannelsBuilder().computeChannels(userDao, teamDao, cardTypeDao, projectDao) .forEach( channel -> { addAsDeleted(messagesByChannel, channel, object); @@ -178,7 +177,7 @@ public static PrecomputedWsMessages prepareWsMessageForAdmins( UserDao userDao, WsMessage message ) throws EncodeException { - return prepareWsMessage(userDao, null, null, new ForAdminChannelsBuilder(), message); + return prepareWsMessage(userDao, null, null, null, new ForAdminChannelsBuilder(), message); } /** @@ -187,23 +186,23 @@ public static PrecomputedWsMessages prepareWsMessageForAdmins( * @param userDao provide userDao to resolve nested channels * @param teamDao provide teamDao to resolve nested channels * @param cardTypeDao provide cardTypeDao to resolve nested channels + * @param projectDao provide projectDao to resolve nested channels * @param channelBuilder the channel builder that defines which channels must be used * @param message the message - * * @return the precomputedMessage - * * @throws EncodeException if json-encoding failed */ public static PrecomputedWsMessages prepareWsMessage( UserDao userDao, TeamMemberDao teamDao, CardTypeDao cardTypeDao, + ProjectDao projectDao, ChannelsBuilder channelBuilder, WsMessage message ) throws EncodeException { Map> messagesByChannel = new HashMap<>(); - channelBuilder.computeChannels(userDao, teamDao, cardTypeDao).forEach(channel -> { + channelBuilder.computeChannels(userDao, teamDao, cardTypeDao, projectDao).forEach(channel -> { messagesByChannel.put(channel, List.of(message)); }); diff --git a/colab-api/src/main/java/ch/colabproject/colab/api/ws/channel/tool/ChannelsBuilders.java b/colab-api/src/main/java/ch/colabproject/colab/api/ws/channel/tool/ChannelsBuilders.java index 78726e69bbc..bafdc2119b4 100644 --- a/colab-api/src/main/java/ch/colabproject/colab/api/ws/channel/tool/ChannelsBuilders.java +++ b/colab-api/src/main/java/ch/colabproject/colab/api/ws/channel/tool/ChannelsBuilders.java @@ -11,6 +11,7 @@ import ch.colabproject.colab.api.model.user.Account; import ch.colabproject.colab.api.model.user.User; import ch.colabproject.colab.api.persistence.jpa.card.CardTypeDao; +import ch.colabproject.colab.api.persistence.jpa.project.ProjectDao; import ch.colabproject.colab.api.persistence.jpa.team.TeamMemberDao; import ch.colabproject.colab.api.persistence.jpa.user.UserDao; import ch.colabproject.colab.api.ws.channel.model.BlockChannel; @@ -19,6 +20,7 @@ import ch.colabproject.colab.api.ws.channel.model.UserChannel; import ch.colabproject.colab.api.ws.channel.model.WebsocketChannel; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -45,13 +47,14 @@ public static abstract class ChannelsBuilder { * * @param userDao the dao to fetch users * @param teamDao the dao to fetch team members - * @param cardTypeDao the dao to fetch card type + * @param cardTypeDao the dao to fetch card types + * @param projectDao the dao to fetch projects * * @return all channels to use for propagation */ public Set computeChannels(UserDao userDao, TeamMemberDao teamDao, - CardTypeDao cardTypeDao) { - return build(userDao, teamDao, cardTypeDao); + CardTypeDao cardTypeDao, ProjectDao projectDao) { + return build(userDao, teamDao, cardTypeDao, projectDao); } /** @@ -59,12 +62,13 @@ public Set computeChannels(UserDao userDao, TeamMemberDao team * * @param userDao the dao to fetch users * @param teamDao the dao to fetch the team members - * @param cardTypeDao the dao to fetch card type + * @param cardTypeDao the dao to fetch card types + * @param projectDao the dao to fetch projects * * @return all channels to use for propagation */ abstract protected Set build(UserDao userDao, TeamMemberDao teamDao, - CardTypeDao cardTypeDao); + CardTypeDao cardTypeDao, ProjectDao projectDao); } /** @@ -73,7 +77,7 @@ abstract protected Set build(UserDao userDao, TeamMemberDao te public static class EmptyChannelBuilder extends ChannelsBuilder { @Override protected Set build(UserDao userDao, TeamMemberDao teamDao, - CardTypeDao cardTypeDao) { + CardTypeDao cardTypeDao, ProjectDao projectDao) { return Set.of(); } } @@ -96,7 +100,7 @@ public BlockChannelBuilder(Long blockId) { @Override protected Set build(UserDao userDao, TeamMemberDao teamDao, - CardTypeDao cardTypeDao) { + CardTypeDao cardTypeDao, ProjectDao projectDao) { return Set.of(BlockChannel.build(blockId)); } } @@ -128,7 +132,7 @@ public ProjectContentChannelBuilder(Long projectId) { @Override protected Set build(UserDao userDao, TeamMemberDao teamDao, - CardTypeDao cardTypeDao) { + CardTypeDao cardTypeDao, ProjectDao projectDao) { return Set.of(ProjectContentChannel.build(projectId)); } } @@ -151,12 +155,13 @@ public AboutProjectOverviewChannelsBuilder(Project project) { @Override protected Set build(UserDao userDao, TeamMemberDao teamDao, - CardTypeDao cardTypeDao) { + CardTypeDao cardTypeDao, ProjectDao projectDao) { Set channels = new HashSet<>(); if (project != null) { channels.addAll(buildTeammemberChannels(this.project)); - + channels.addAll(buildInstanceMakerChannels(this.project)); + if (project.isOrWasGlobal()) { channels.add(BroadcastChannel.build()); } else { @@ -175,7 +180,7 @@ public static class ForAdminChannelsBuilder extends ChannelsBuilder { @Override protected Set build(UserDao userDao, TeamMemberDao teamDao, - CardTypeDao cardTypeDao) { + CardTypeDao cardTypeDao, ProjectDao projectDao) { return buildAdminChannels(userDao); } } @@ -198,13 +203,14 @@ public AboutUserChannelsBuilder(User user) { @Override protected Set build(UserDao userDao, TeamMemberDao teamDao, - CardTypeDao cardTypeDao) { + CardTypeDao cardTypeDao, ProjectDao projectDao) { Set channels = new HashSet<>(); if (user != null) { channels.add(UserChannel.build(user)); channels.addAll(buildTeammatesChannels(user, teamDao)); + channels.addAll(buildInstanceMakersChannels(user, projectDao)); channels.addAll(buildAdminChannels(userDao)); } @@ -231,7 +237,7 @@ public AboutAccountChannelsBuilder(Account account) { @Override protected Set build(UserDao userDao, TeamMemberDao teamDao, - CardTypeDao cardTypeDao) { + CardTypeDao cardTypeDao, ProjectDao projectDao) { Set channels = new HashSet<>(); if (account.getUser() != null) { @@ -262,7 +268,7 @@ public AboutCardTypeChannelsBuilder(AbstractCardType cardType) { @Override protected Set build(UserDao userDao, TeamMemberDao teamDao, - CardTypeDao cardTypeDao) { + CardTypeDao cardTypeDao, ProjectDao projectDao) { return buildCardTypeInProjectChannel(cardType, userDao, cardTypeDao); } } @@ -300,6 +306,28 @@ private static Set buildTeammatesChannels(User user, TeamMembe return channels; } + /** + * Build a channel for each user with whom the model is shared + * + * @param user the user + * + * @return a set of channels : one user channel for each user with whom the model is shared + */ + private static Set buildInstanceMakersChannels(User user, ProjectDao projectDao) { + Set channels = new HashSet<>(); + + // find all models of the user + List models = projectDao.findProjectsByTeamMember(user.getId()).stream() + .filter(Project::isModel).collect(Collectors.toList()); + + // for each model, add the channel of every instance maker + models.forEach(model -> { + channels.addAll(buildInstanceMakerChannels(model)); + }); + + return channels; + } + /** * Build a channel for each team member of the project * @@ -315,6 +343,20 @@ private static Set buildTeammemberChannels(Project project) { .collect(Collectors.toSet()); } + /** + * Build a channel for each user with whom the model is shared + * + * @param project the project + * + * @return a set of user channels : one for each user + */ + private static Set buildInstanceMakerChannels(Project project) { + return project.getInstanceMakers().stream() + .filter(participant -> participant.getUser() != null) + .map(member -> UserChannel.build(member.getUser())) + .collect(Collectors.toSet()); + } + /** * Build all channels needed when a card type is changed * @@ -322,7 +364,7 @@ private static Set buildTeammemberChannels(Project project) { * @param userDao to fetch the admin * @param cardTypeDao to fetch the references of a card type * - * @return + * @return a set of user channels */ private static Set buildCardTypeInProjectChannel( AbstractCardType cardType, UserDao userDao, diff --git a/colab-api/src/main/node/colab-yjs/package.json b/colab-api/src/main/node/colab-yjs/package.json index 8d04cb3594d..8836666b7fd 100644 --- a/colab-api/src/main/node/colab-yjs/package.json +++ b/colab-api/src/main/node/colab-yjs/package.json @@ -27,8 +27,7 @@ "express": "^4.18.2", "lib0": "^0.2.85", "lodash": "^4.17.21", - "mongoist": "^2.7.0", - "mongojs": "^3.1.0", + "mongodb": "^6.3.0", "pino": "^8.15.1", "pino-pretty": "^10.2.0", "query-string": "^8.1.0", diff --git a/colab-api/src/main/node/colab-yjs/src/index.ts b/colab-api/src/main/node/colab-yjs/src/index.ts index 360e4943f92..a6ea3c77d71 100644 --- a/colab-api/src/main/node/colab-yjs/src/index.ts +++ b/colab-api/src/main/node/colab-yjs/src/index.ts @@ -41,28 +41,29 @@ const mongoHostHttp = mongoHost.replace('mongodb', 'http'); // MongoDriver const mongoDriver = new MongodbPersistence(mongoHost, { collectionName: mongoCollection, - flushSize: 100, + flushSize: 400, multipleCollections: false, }); setPersistence({ provider: mongoDriver, - bindState: async (docName, ydoc) => { - const persistedYdoc = await mongoDriver.getYDoc(docName); - const persistedStateVector = Y.encodeStateVector(persistedYdoc); - const diff = Y.encodeStateAsUpdate(ydoc, persistedStateVector); + bindState: async (docName, yDoc) => { + const persistedYDoc = await mongoDriver.getYDoc(docName); + const persistedStateVector = Y.encodeStateVector(persistedYDoc); + const diff = Y.encodeStateAsUpdate(yDoc, persistedStateVector); - if (diff.reduce((previousValue, currentValue) => previousValue + currentValue, 0) > 0) + if (diff.reduce((previousValue, currentValue) => previousValue + currentValue, 0) > 0) { mongoDriver.storeUpdate(docName, diff); + } - Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(persistedYdoc)); + Y.applyUpdate(yDoc, Y.encodeStateAsUpdate(persistedYDoc)); - ydoc.on('update', async update => { + yDoc.on('update', async update => { logger.debug('update on docName: ' + docName); mongoDriver.storeUpdate(docName, update); }); - persistedYdoc.destroy(); + persistedYDoc.destroy(); }, writeState: async (docName, ydoc) => { await mongoDriver.flushDocument(docName); @@ -96,6 +97,7 @@ app.get('/healthz', async (request: Request, response: Response) => { response.status(200).send('Server is healthy'); } catch (err) { + logger.error('Payara Authorization or MongoDB connection failed'); logger.error(err); } }); @@ -107,6 +109,7 @@ app.delete('/delete', async (request: Request, response: Response) => { response.status(200).send(`Document ${docName} deleted`); } catch (err) { + logger.error('Delete operation failed on docName: ' + getDocNameFromUrl(request.url)); logger.error(err); } }); diff --git a/colab-api/src/main/node/colab-yjs/src/mongo/mongo-adapter.ts b/colab-api/src/main/node/colab-yjs/src/mongo/mongo-adapter.ts index d7975a81de7..a06a09d85cb 100644 --- a/colab-api/src/main/node/colab-yjs/src/mongo/mongo-adapter.ts +++ b/colab-api/src/main/node/colab-yjs/src/mongo/mongo-adapter.ts @@ -5,49 +5,57 @@ * Licensed under the MIT License */ +// Taken from https://github.com/MaxNoetzold/y-mongodb-provider + // @ts-nocheck -import mongoist from 'mongoist'; -import mongojs from 'mongojs'; -import logger from '../utils/logger.js'; +import { MongoClient } from 'mongodb'; + +function parseMongoDBConnectionString(connectionString) { + const url = new URL(connectionString); + const database = url.pathname.slice(1); + url.pathname = '/'; + + return { + database, + linkWithoutDatabase: url.toString(), + }; +} export class MongoAdapter { - location: string; - collection: string; - multipleCollections: boolean; - db: any; - - constructor( - location: string, - { collection, multipleCollections }: { collection: string; multipleCollections: boolean }, - ) { - this.location = location; + /** + * Create a MongoAdapter instance. + * @param {string} connectionString + * @param {object} opts + * @param {string} opts.collection Name of the collection where all documents are stored. + * @param {boolean} opts.multipleCollections When set to true, each document gets an own + * collection (instead of all documents stored in the same one). + * When set to true, the option $collection gets ignored. + */ + constructor(connectionString, { collection, multipleCollections }) { this.collection = collection; this.multipleCollections = multipleCollections; - this.db = null; - this.open(); - this.ping(); - } + const connectionParams = parseMongoDBConnectionString(connectionString); + this.mongoUrl = connectionParams.linkWithoutDatabase; + this.databaseName = connectionParams.database; + this.client = new MongoClient(this.mongoUrl); + /* + client.connect() is optional since v4.7 + "However, MongoClient.connect can still be called manually and remains useful for + learning about misconfiguration (auth, server not started, connection string correctness) + early in your application's startup." - /** - * Open the connection to MongoDB instance. - */ - open() { - let mongojsDb; - if (this.multipleCollections) { - mongojsDb = mongojs(this.location); - } else { - mongojsDb = mongojs(this.location, [this.collection]); - } - this.db = mongoist(mongojsDb); + I will not use it for now, but may change that in the future. + */ + this.db = this.client.db(this.databaseName); } /** * Get the MongoDB collection name for any docName - * @param {object} [opts] - * @param {string} [opts.docName] + * @param {object} opts + * @param {string} opts.docName * @returns {string} collectionName */ - _getCollectionName({ docName }: { docName: string }): string { + _getCollectionName({ docName }) { if (this.multipleCollections) { return docName; } else { @@ -60,8 +68,9 @@ export class MongoAdapter { * @param {object} query * @returns {Promise} */ - get(query: { docName: string }): Promise { - return this.db[this._getCollectionName(query)].findOne(query); + get(query) { + const collection = this.db.collection(this._getCollectionName(query)); + return collection.findOne(query); } /** @@ -70,18 +79,15 @@ export class MongoAdapter { * @param {object} values * @returns {Promise} Stored document */ - put(query: { docName: any; version?: any }, values: { value: any }): Promise { + async put(query, values) { if (!query.docName || !query.version || !values.value) { throw new Error('Document and version must be provided'); } - // findAndModify with upsert:true should simulate leveldb put better - return this.db[this._getCollectionName(query)].findAndModify({ - query, - update: { ...query, ...values }, - upsert: true, - new: true, - }); + const collection = this.db.collection(this._getCollectionName(query)); + + await collection.updateOne(query, { $set: values }, { upsert: true }); + return this.get(query); } /** @@ -89,9 +95,20 @@ export class MongoAdapter { * @param {object} query * @returns {Promise} Contains status of the operation */ - del(query: { docName: string }): Promise { - const bulk = this.db[this._getCollectionName(query)].initializeOrderedBulkOp(); - bulk.find(query).remove(); + del(query) { + const collection = this.db.collection(this._getCollectionName(query)); + + /* + Note from mongodb v4.7 release notes: + "It's a known limitation that explicit sessions (client.startSession) and + initializeOrderedBulkOp, initializeUnorderedBulkOp cannot be used until + MongoClient.connect is first called. + Look forward to a future patch release that will correct these inconsistencies." + + I dont know yet if this is a problem for me here. + */ + const bulk = collection.initializeOrderedBulkOp(); + bulk.find(query).delete(); return bulk.execute(); } @@ -103,29 +120,32 @@ export class MongoAdapter { * @param {boolean} [opts.reverse] * @returns {Promise>} */ - readAsCursor( - query: { docName: string }, - { limit, reverse }: { limit?: number; reverse?: boolean } = {}, - ): Promise> { - let curs = this.db[this._getCollectionName(query)].findAsCursor(query); - if (reverse) curs = curs.sort({ clock: -1 }); - if (limit) curs = curs.limit(limit); + readAsCursor(query, opts = {}) { + const { limit = 0, reverse = false } = opts; + + const collection = this.db.collection(this._getCollectionName(query)); + + /** @type {{ clock: 1 | -1, part: 1 | -1 }} */ + const sortQuery = reverse ? { clock: -1, part: 1 } : { clock: 1, part: 1 }; + const curs = collection.find(query).sort(sortQuery).limit(limit); + return curs.toArray(); } /** * Close connection to MongoDB instance. */ - close() { - this.db.close(); + async close() { + await this.client.close(); } /** * Get all collection names stored on the MongoDB instance. - * @returns {Promise>} + * @returns {Promise} */ - getCollectionNames(): Promise> { - return this.db.getCollectionNames(); + async getCollectionNames() { + const collectionInfos = await this.db.listCollections().toArray(); + return collectionInfos.map(c => c.name); } /** @@ -133,24 +153,14 @@ export class MongoAdapter { */ async flush() { await this.db.dropDatabase(); - await this.db.close(); + await this.client.close(); } /** * Delete collection * @param {string} collectionName */ - dropCollection(collectionName: string) { - return this.db[collectionName].drop(); - } - - /** - * Check if DB connection alive - */ - async ping() { - const result = await this.db.runCommand({ ping: 1 }); - if (result) { - logger.info('[MongoAdapter]: MongoDB connected'); - } + dropCollection(collectionName) { + return this.db.collection(collectionName).drop(); } } diff --git a/colab-api/src/main/node/colab-yjs/src/mongo/utils.ts b/colab-api/src/main/node/colab-yjs/src/mongo/utils.ts index f03abe325c1..7891a555391 100644 --- a/colab-api/src/main/node/colab-yjs/src/mongo/utils.ts +++ b/colab-api/src/main/node/colab-yjs/src/mongo/utils.ts @@ -5,13 +5,17 @@ * Licensed under the MIT License */ -import { Buffer } from 'buffer'; +// Taken from https://github.com/MaxNoetzold/y-mongodb-provider + +// @ts-nocheck +import * as Y from 'yjs'; import * as binary from 'lib0/binary'; -import * as decoding from 'lib0/decoding'; import * as encoding from 'lib0/encoding'; -import * as Y from 'yjs'; +import * as decoding from 'lib0/decoding'; +import { Buffer } from 'buffer'; export const PREFERRED_TRIM_SIZE = 400; +const MAX_DOCUMENT_SIZE = 15000000; // ~15MB (plus space for metadata) /** * Remove all documents from db with Clock between $from and $to @@ -22,12 +26,7 @@ export const PREFERRED_TRIM_SIZE = 400; * @param {number} to lower than (not equal) * @return {Promise} */ -export const clearUpdatesRange = async ( - db: any, - docName: string, - from: number, - to: number, -): Promise => +export const clearUpdatesRange = async (db, docName, from, to) => db.del({ docName, clock: { @@ -39,10 +38,10 @@ export const clearUpdatesRange = async ( /** * Create a unique key for a update message. * @param {string} docName - * @param {number} clock must be unique + * @param {number} [clock] must be unique * @return {Object} [opts.version, opts.docName, opts.action, opts.clock] */ -export const createDocumentUpdateKey = (docName: string, clock?: number): object => { +export const createDocumentUpdateKey = (docName, clock) => { if (clock !== undefined) { return { version: 'v1', @@ -64,7 +63,7 @@ export const createDocumentUpdateKey = (docName: string, clock?: number): object * @param {string} docName * @return {Object} [opts.docName, opts.version] */ -export const createDocumentStateVectorKey = (docName: string): object => ({ +export const createDocumentStateVectorKey = docName => ({ docName, version: 'v1_sv', }); @@ -74,7 +73,7 @@ export const createDocumentStateVectorKey = (docName: string): object => ({ * @param {string} metaKey * @return {Object} [opts.docName, opts.version, opts.docType, opts.metaKey] */ -export const createDocumentMetaKey = (docName: string, metaKey: string): object => ({ +export const createDocumentMetaKey = (docName, metaKey) => ({ version: 'v1', docName, metaKey: `meta_${metaKey}`, @@ -84,27 +83,51 @@ export const createDocumentMetaKey = (docName: string, metaKey: string): object * @param {any} db * @param {object} query * @param {object} opts - * @return {Promise>} + * @return {Promise} */ -export const getMongoBulkData = (db: any, query: object, opts: object): Promise> => - db.readAsCursor(query, opts); +export const _getMongoBulkData = (db, query, opts) => db.readAsCursor(query, opts); /** * @param {any} db * @return {Promise} */ -export const flushDB = (db: any) => db.flush(); +export const flushDB = db => db.flush(); /** * Convert the mongo document array to an array of values (as buffers) * - * @param {>} docs - * @return {>} + * @param {any[]} docs + * @return {Buffer[]} */ -const _convertMongoUpdates = (docs: any) => { +const _convertMongoUpdates = docs => { if (!Array.isArray(docs) || !docs.length) return []; - return docs.map(update => update.value.buffer); + const updates = []; + for (let i = 0; i < docs.length; i++) { + const doc = docs[i]; + if (!doc.part) { + updates.push(doc.value.buffer); + } else if (doc.part === 1) { + // merge the docs together that got split because of mongodb size limits + const parts = [Buffer.from(doc.value.buffer)]; + let j; + let currentPartId = doc.part; + for (j = i + 1; j < docs.length; j++) { + const part = docs[j]; + if (part.clock === doc.clock) { + if (currentPartId !== part.part - 1) { + throw new Error('Couldnt merge updates together because a part is missing!'); + } + parts.push(Buffer.from(part.value.buffer)); + currentPartId = part.part; + } else { + break; + } + } + updates.push(Buffer.concat(parts)); + } + } + return updates; }; /** * Get all document updates for a specific document. @@ -112,14 +135,10 @@ const _convertMongoUpdates = (docs: any) => { * @param {any} db * @param {string} docName * @param {any} [opts] - * @return {Promise>} + * @return {Promise} */ -export const getMongoUpdates = async ( - db: any, - docName: string, - opts: any = {}, -): Promise> => { - const docs = await getMongoBulkData(db, createDocumentUpdateKey(docName), opts); +export const getMongoUpdates = async (db, docName, opts = {}) => { + const docs = await _getMongoBulkData(db, createDocumentUpdateKey(docName), opts); return _convertMongoUpdates(docs); }; @@ -128,8 +147,8 @@ export const getMongoUpdates = async ( * @param {string} docName * @return {Promise} Returns -1 if this document doesn't exist yet */ -export const getCurrentUpdateClock = (db: any, docName: string): Promise => - getMongoBulkData( +export const getCurrentUpdateClock = (db, docName) => + _getMongoBulkData( db, { ...createDocumentUpdateKey(docName, 0), @@ -154,7 +173,7 @@ export const getCurrentUpdateClock = (db: any, docName: string): Promise * @param {number} clock current clock of the document so we can determine * when this statevector was created */ -export const writeStateVector = async (db: any, docName: string, sv: Uint8Array, clock: number) => { +export const writeStateVector = async (db, docName, sv, clock) => { const encoder = encoding.createEncoder(); encoding.writeVarUint(encoder, clock); encoding.writeVarUint8Array(encoder, sv); @@ -169,11 +188,7 @@ export const writeStateVector = async (db: any, docName: string, sv: Uint8Array, * @param {Uint8Array} update * @return {Promise} Returns the clock of the stored update */ -export const storeUpdate = async ( - db: any, - docName: string, - update: Uint8Array, -): Promise => { +export const storeUpdate = async (db, docName, update) => { const clock = await getCurrentUpdateClock(db, docName); if (clock === -1) { // make sure that a state vector is always written, so we can search for available documents @@ -183,9 +198,29 @@ export const storeUpdate = async ( await writeStateVector(db, docName, sv, 0); } - await db.put(createDocumentUpdateKey(docName, clock + 1), { - value: Buffer.from(update), - }); + const value = Buffer.from(update); + // mongodb has a maximum document size of 16MB; + // if our buffer exceeds it, we store the update in multiple documents + if (value.length <= MAX_DOCUMENT_SIZE) { + await db.put(createDocumentUpdateKey(docName, clock + 1), { + value, + }); + } else { + const totalChunks = Math.ceil(value.length / MAX_DOCUMENT_SIZE); + + const putPromises = []; + for (let i = 0; i < totalChunks; i++) { + const start = i * MAX_DOCUMENT_SIZE; + const end = Math.min(start + MAX_DOCUMENT_SIZE, value.length); + const chunk = value.subarray(start, end); + + putPromises.push( + db.put({ ...createDocumentUpdateKey(docName, clock + 1), part: i + 1 }, { value: chunk }), + ); + } + + await Promise.all(putPromises); + } return clock + 1; }; @@ -197,9 +232,7 @@ export const storeUpdate = async ( * @param {Array} updates * @return {{update:Uint8Array, sv: Uint8Array}} */ -export const mergeUpdates = ( - updates: Array, -): { update: Uint8Array; sv: Uint8Array } => { +export const mergeUpdates = updates => { const ydoc = new Y.Doc(); ydoc.transact(() => { for (let i = 0; i < updates.length; i++) { @@ -213,7 +246,7 @@ export const mergeUpdates = ( * @param {Uint8Array} buf * @return {{ sv: Uint8Array, clock: number }} */ -export const decodeMongodbStateVector = (buf: Uint8Array): { sv: Uint8Array; clock: number } => { +export const decodeMongodbStateVector = buf => { let decoder; if (Buffer.isBuffer(buf)) { decoder = decoding.createDecoder(buf); @@ -231,7 +264,7 @@ export const decodeMongodbStateVector = (buf: Uint8Array): { sv: Uint8Array; clo * @param {any} db * @param {string} docName */ -export const readStateVector = async (db: any, docName: string) => { +export const readStateVector = async (db, docName) => { const doc = await db.get({ ...createDocumentStateVectorKey(docName) }); if (!doc?.value) { // no state vector created yet or no document exists @@ -240,7 +273,7 @@ export const readStateVector = async (db: any, docName: string) => { return decodeMongodbStateVector(doc.value); }; -export const getAllSVDocs = async (db: any) => db.readAsCursor({ version: 'v1_sv' }); +export const getAllSVDocs = async db => db.readAsCursor({ version: 'v1_sv' }); /** * Merge all MongoDB documents of the same yjs document together. @@ -250,12 +283,7 @@ export const getAllSVDocs = async (db: any) => db.readAsCursor({ version: 'v1_sv * @param {Uint8Array} stateVector * @return {Promise} returns the clock of the flushed doc */ -export const flushDocument = async ( - db: any, - docName: string, - stateAsUpdate: Uint8Array, - stateVector: Uint8Array, -): Promise => { +export const flushDocument = async (db, docName, stateAsUpdate, stateVector) => { const clock = await storeUpdate(db, docName, stateAsUpdate); await writeStateVector(db, docName, stateVector, clock); await clearUpdatesRange(db, docName, 0, clock); diff --git a/colab-api/src/main/node/colab-yjs/src/mongo/y-mongodb.ts b/colab-api/src/main/node/colab-yjs/src/mongo/y-mongodb.ts index 73b5d3cdcbc..6158f95d3fc 100644 --- a/colab-api/src/main/node/colab-yjs/src/mongo/y-mongodb.ts +++ b/colab-api/src/main/node/colab-yjs/src/mongo/y-mongodb.ts @@ -5,10 +5,12 @@ * Licensed under the MIT License */ +// Taken from https://github.com/MaxNoetzold/y-mongodb-provider + // @ts-nocheck +import * as Y from 'yjs'; import * as binary from 'lib0/binary'; import * as promise from 'lib0/promise'; -import * as Y from 'yjs'; import { MongoAdapter } from './mongo-adapter.js'; import * as U from './utils.js'; @@ -16,23 +18,17 @@ export class MongodbPersistence { /** * Create a y-mongodb persistence instance. * @param {string} location The connection string for the MongoDB instance. - * @param {object} [opts=] Additional optional parameters. - * @param {string} [opts.collectionName="yjs-writings"] Name of the collection where all + * @param {object} [opts] Additional optional parameters. + * @param {string} [opts.collectionName] Name of the collection where all * documents are stored. Default: "yjs-writings" - * @param {boolean} [opts.multipleCollections=false] When set to true, each document gets + * @param {boolean} [opts.multipleCollections] When set to true, each document gets * an own collection (instead of all documents stored in the same one). When set to true, * the option collectionName gets ignored. Default: false - * @param {number} [opts.flushSize=400] The number of stored transactions needed until + * @param {number} [opts.flushSize] The number of stored transactions needed until * they are merged automatically into one Mongodb document. Default: 400 */ - constructor( - location: string, - { - collectionName = 'yjs-writings', - multipleCollections = false, - flushSize = 400, - }: { collectionName?: string; multipleCollections?: boolean; flushSize?: number } = {}, - ) { + constructor(location, opts = {}) { + const { collectionName = 'yjs-writings', multipleCollections = false, flushSize = 400 } = opts; if (typeof collectionName !== 'string' || !collectionName) { throw new Error( 'Constructor option "collectionName" is not a valid string. Either dont use this option (default is "yjs-writings") or use a valid string! Take a look into the Readme for more information: https://github.com/MaxNoetzold/y-mongodb-provider#persistence--mongodbpersistenceconnectionlink-string-options-object', @@ -52,7 +48,6 @@ export class MongodbPersistence { collection: collectionName, multipleCollections, }); - this.flushSize = flushSize ?? U.PREFERRED_TRIM_SIZE; this.multipleCollections = multipleCollections; @@ -78,36 +73,48 @@ export class MongodbPersistence { } const currTr = this.tr[docName]; + let nextTr = null; - this.tr[docName] = (async () => { + nextTr = (async () => { await currTr; let res = /** @type {any} */ null; try { res = await f(db); } catch (err) { + // eslint-disable-next-line no-console console.warn('Error during saving transaction', err); } + + // once the last transaction for a given docName resolves, remove it from the queue + if (this.tr[docName] === nextTr) { + delete this.tr[docName]; + } + return res; })(); + + this.tr[docName] = nextTr; + return this.tr[docName]; }; } /** - * Create a Y.Doc instance with the data persistent in mongodb. + * Create a Y.Doc instance with the data persisted in mongodb. * Use this to temporarily create a Yjs document to sync changes or extract data. * * @param {string} docName * @return {Promise} */ - getYDoc(docName: string): Promise { + getYDoc(docName) { return this._transact(docName, async db => { const updates = await U.getMongoUpdates(db, docName); const ydoc = new Y.Doc(); ydoc.transact(() => { for (let i = 0; i < updates.length; i++) { Y.applyUpdate(ydoc, updates[i]); + updates[i] = null; } }); if (updates.length > this.flushSize) { @@ -124,7 +131,7 @@ export class MongodbPersistence { * @param {Uint8Array} update * @return {Promise} Returns the clock of the stored update */ - storeUpdate(docName: string, update: Uint8Array): Promise { + storeUpdate(docName, update) { return this._transact(docName, db => U.storeUpdate(db, docName, update)); } @@ -136,7 +143,7 @@ export class MongodbPersistence { * @param {string} docName * @return {Promise} */ - getStateVector(docName: string): Promise { + getStateVector(docName) { return this._transact(docName, async db => { const { clock, sv } = await U.readStateVector(db, docName); let curClock = -1; @@ -161,7 +168,7 @@ export class MongodbPersistence { * @param {string} docName * @param {Uint8Array} stateVector */ - async getDiff(docName: string, stateVector: Uint8Array) { + async getDiff(docName, stateVector) { const ydoc = await this.getYDoc(docName); return Y.encodeStateAsUpdate(ydoc, stateVector); } @@ -172,7 +179,7 @@ export class MongodbPersistence { * @param {string} docName * @return {Promise} */ - clearDocument(docName: string): Promise { + clearDocument(docName) { return this._transact(docName, async db => { if (!this.multipleCollections) { await db.del(U.createDocumentStateVectorKey(docName)); @@ -193,9 +200,9 @@ export class MongodbPersistence { * @param {any} value * @return {Promise} */ - setMeta(docName: string, metaKey: string, value: any): Promise { + setMeta(docName, metaKey, value) { /* Unlike y-leveldb, we simply store the value here without encoding - it in a buffer beforehand. */ + it in a buffer beforehand. */ return this._transact(docName, async db => { await db.put(U.createDocumentMetaKey(docName, metaKey), { value }); }); @@ -209,7 +216,7 @@ export class MongodbPersistence { * @param {string} metaKey * @return {Promise} */ - getMeta(docName: string, metaKey: string): Promise { + getMeta(docName, metaKey) { return this._transact(docName, async db => { const res = await db.get({ ...U.createDocumentMetaKey(docName, metaKey), @@ -228,7 +235,7 @@ export class MongodbPersistence { * @param {string} metaKey * @return {Promise} */ - delMeta(docName: string, metaKey: string): Promise { + delMeta(docName, metaKey) { return this._transact(docName, db => db.del({ ...U.createDocumentMetaKey(docName, metaKey), @@ -239,9 +246,9 @@ export class MongodbPersistence { /** * Retrieve the names of all stored documents. * - * @return {Promise>} + * @return {Promise} */ - getAllDocNames(): Promise> { + getAllDocNames() { return this._transact('global', async db => { if (this.multipleCollections) { // get all collection names from db @@ -260,10 +267,10 @@ export class MongodbPersistence { * You can use this to sync two y-leveldb instances. * !Note: The state vectors might be outdated if the associated document * is not yet flushed. So use with caution. - * @return {Promise>} + * @return {Promise<{ name: string, sv: Uint8Array, clock: number }[]>} * @todo may not work? */ - getAllDocStateVectors(): Promise> { + getAllDocStateVectors() { return this._transact('global', async db => { const docs = await U.getAllSVDocs(db); return docs.map(doc => { @@ -281,7 +288,7 @@ export class MongodbPersistence { * @param {string} docName * @return {Promise} */ - flushDocument(docName: string): Promise { + flushDocument(docName) { return this._transact(docName, async db => { const updates = await U.getMongoUpdates(db, docName); const { update, sv } = U.mergeUpdates(updates); @@ -293,9 +300,19 @@ export class MongodbPersistence { * Delete the whole yjs mongodb * @return {Promise} */ - flushDB(): Promise { + flushDB() { return this._transact('global', async db => { await U.flushDB(db); }); } + + /** + * Closes open database connection + * @returns {Promise} + */ + destroy() { + return this._transact('global', async db => { + await db.close(); + }); + } } diff --git a/colab-api/src/main/node/colab-yjs/src/server/utils.ts b/colab-api/src/main/node/colab-yjs/src/server/utils.ts index 2291511433e..7d050dc1c2e 100644 --- a/colab-api/src/main/node/colab-yjs/src/server/utils.ts +++ b/colab-api/src/main/node/colab-yjs/src/server/utils.ts @@ -12,11 +12,14 @@ import * as Y from 'yjs'; import { decoding, encoding, map } from 'lib0'; import lodash from 'lodash'; + const { debounce } = lodash; import { getDocNameFromUrl } from '../utils/utils.js'; import { callbackHandler, isCallbackSet } from './callback.js'; import logger from '../utils/logger.js'; +import WebSocket from 'ws'; +import http from 'http'; const CALLBACK_DEBOUNCE_WAIT = Number(process.env.CALLBACK_DEBOUNCE_WAIT) || 2000; const CALLBACK_DEBOUNCE_MAXWAIT = Number(process.env.CALLBACK_DEBOUNCE_MAXWAIT) || 10000; @@ -37,24 +40,6 @@ interface Persistence { } let persistence: Persistence | null = null; -if (typeof persistenceDir === 'string') { - // @ts-ignore - const LeveldbPersistence = require('y-leveldb').LeveldbPersistence; - const ldb = new LeveldbPersistence(persistenceDir); - persistence = { - provider: ldb, - bindState: async (docName: string, ydoc: WSSharedDoc) => { - const persistedYdoc = await ldb.getYDoc(docName); - const newUpdates = Y.encodeStateAsUpdate(ydoc); - ldb.storeUpdate(docName, newUpdates); - Y.applyUpdate(ydoc, Y.encodeStateAsUpdate(persistedYdoc)); - ydoc.on('update', update => { - ldb.storeUpdate(docName, update); - }); - }, - writeState: async (docName: string, ydoc: WSSharedDoc) => {}, - }; -} export const setPersistence = (persistence_: Persistence) => { persistence = persistence_; @@ -147,7 +132,7 @@ export const getYDoc = (docname: string, gc = true) => return doc; }); -const messageListener = (conn: any, doc: WSSharedDoc, message: Uint8Array) => { +const messageListener = (conn: WebSocket, doc: WSSharedDoc, message: Uint8Array) => { try { const encoder = encoding.createEncoder(); const decoder = decoding.createDecoder(message); @@ -179,12 +164,13 @@ const messageListener = (conn: any, doc: WSSharedDoc, message: Uint8Array) => { } }; -const closeConn = (doc: WSSharedDoc, conn: any) => { +const closeConn = (doc: WSSharedDoc, conn: WebSocket) => { if (doc.conns.has(conn)) { - // @ts-ignore - const controlledIds: Set = doc.conns.get(conn); + const controlledIds: Set | undefined = doc.conns.get(conn); doc.conns.delete(conn); - awarenessProtocol.removeAwarenessStates(doc.awareness, Array.from(controlledIds), null); + if (controlledIds) { + awarenessProtocol.removeAwarenessStates(doc.awareness, Array.from(controlledIds), null); + } if (doc.conns.size === 0 && persistence !== null) { // if persisted, we store state and destroy ydocument persistence.writeState(doc.name, doc).then(() => { @@ -212,8 +198,8 @@ const send = (doc: WSSharedDoc, conn: any, m: Uint8Array) => { const pingTimeout = 1000; export const setupWSConnection = ( - conn: any, - req: any, + conn: WebSocket, + req: http.IncomingMessage, { docName = getDocNameFromUrl(req.url!), gc = true }: any = {}, ) => { conn.binaryType = 'arraybuffer'; @@ -221,7 +207,7 @@ export const setupWSConnection = ( const doc = getYDoc(docName, gc); doc.conns.set(conn, new Set()); // listen and reply to events - logger.debug('connection on docName: ' + docName) + logger.debug('connection opened on docName: ' + docName); conn.on('message', (message: ArrayBuffer) => messageListener(conn, doc, new Uint8Array(message))); // Check if connection is still alive @@ -244,6 +230,7 @@ export const setupWSConnection = ( }, pingTimeout); conn.on('close', () => { closeConn(doc, conn); + logger.debug('connection closed on docName: ' + docName); clearInterval(pingInterval); }); conn.on('pong', () => { diff --git a/colab-api/src/main/node/colab-yjs/yarn.lock b/colab-api/src/main/node/colab-yjs/yarn.lock index f630e562e18..8da4386aa5c 100644 --- a/colab-api/src/main/node/colab-yjs/yarn.lock +++ b/colab-api/src/main/node/colab-yjs/yarn.lock @@ -10,7 +10,7 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" -"@babel/generator@^7.23.0", "@babel/generator@^7.4.0": +"@babel/generator@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== @@ -66,12 +66,12 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.22.15", "@babel/parser@^7.23.0", "@babel/parser@^7.4.3": +"@babel/parser@^7.22.15", "@babel/parser@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== -"@babel/template@^7.22.15", "@babel/template@^7.4.0": +"@babel/template@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== @@ -80,7 +80,7 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/traverse@^7.23.2", "@babel/traverse@^7.4.3": +"@babel/traverse@^7.23.2": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== @@ -96,7 +96,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.4.0": +"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== @@ -152,6 +152,13 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@mongodb-js/saslprep@^1.1.0": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz#24ec1c4915a65f5c506bb88c081731450d91bb1c" + integrity sha512-8zJ8N1x51xo9hwPh6AWnKdLGEC5N3lDa6kms1YHmFBoRhTpJR6HG8wWk0td1MVCu9cD4YBrvjZEtd5Obw0Fbnw== + dependencies: + sparse-bitfield "^3.0.3" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -264,6 +271,18 @@ "@types/mime" "*" "@types/node" "*" +"@types/webidl-conversions@*": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz#1306dbfa53768bcbcfc95a1c8cde367975581859" + integrity sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA== + +"@types/whatwg-url@^11.0.2": + version "11.0.4" + resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-11.0.4.tgz#ffed0dc8d89d91f62e3f368fcbda222a487c4f63" + integrity sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw== + dependencies: + "@types/webidl-conversions" "*" + "@types/ws@^8.5.4": version "8.5.6" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.6.tgz#e9ad51f0ab79b9110c50916c9fcbddc36d373065" @@ -301,12 +320,7 @@ acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== -ansi-regex@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" - integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -321,30 +335,11 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -append-transform@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" - integrity sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw== - dependencies: - default-require-extensions "^2.0.0" - -archy@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" - integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== - arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -370,14 +365,6 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bl@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" - integrity sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g== - dependencies: - readable-stream "^2.3.5" - safe-buffer "^5.1.1" - body-parser@1.20.1: version "1.20.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" @@ -418,10 +405,10 @@ braces@~3.0.2: dependencies: fill-range "^7.0.1" -bson@^1.1.4: - version "1.1.6" - resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.6.tgz#fb819be9a60cd677e0853aee4ca712a785d6618a" - integrity sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg== +bson@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/bson/-/bson-6.2.0.tgz#4b6acafc266ba18eeee111373c2699304a9ba0a3" + integrity sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q== buffer@^6.0.3: version "6.0.3" @@ -436,16 +423,6 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -caching-transform@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-3.0.2.tgz#601d46b91eca87687a281e71cef99791b0efca70" - integrity sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w== - dependencies: - hasha "^3.0.0" - make-dir "^2.0.0" - package-hash "^3.0.0" - write-file-atomic "^2.4.2" - call-bind@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -454,11 +431,6 @@ call-bind@^1.0.0: function-bind "^1.1.1" get-intrinsic "^1.0.2" -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -483,15 +455,6 @@ chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -509,11 +472,6 @@ colorette@^2.0.7: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -531,11 +489,6 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== -convert-source-map@^1.6.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -546,35 +499,11 @@ cookie@0.5.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cp-file@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-6.2.0.tgz#40d5ea4a1def2a9acdd07ba5c0b0246ef73dc10d" - integrity sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA== - dependencies: - graceful-fs "^4.1.2" - make-dir "^2.0.0" - nested-error-stacks "^2.0.0" - pify "^4.0.1" - safe-buffer "^5.0.1" - create-require@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-spawn@^4: - version "4.0.2" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" - integrity sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA== - dependencies: - lru-cache "^4.0.1" - which "^1.2.9" - dateformat@^4.6.3: version "4.6.3" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" @@ -594,35 +523,18 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.4: +debug@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== - decode-uri-component@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz#2ac4859663c704be22bf7db760a1494a49ab2cc5" integrity sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ== -default-require-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" - integrity sha512-B0n2zDIXpzLzKeoEozorDSa1cHc1t0NjmxP0zuAxbizNU2MBqYJJKYXrrFdKuQliojXynrxgd7l4ahfg/+aA5g== - dependencies: - strip-bom "^3.0.0" - -denque@^1.4.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" - integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== - depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -643,21 +555,11 @@ dotenv@^16.3.1: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== -each-series@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/each-series/-/each-series-1.0.0.tgz#f886e6c66dfdb25ef1fe73564146ee5cb478afcb" - integrity sha512-4MQloCGGCmT5GJZK5ibgJSvTK1c1QSrNlDvLk6fEyRxjZnXjl+NNFfzhfXpmnWh33Owc9D9klrdzCUi7yc9r4Q== - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -670,18 +572,6 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es6-error@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== - escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -692,11 +582,6 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" @@ -789,30 +674,6 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -find-cache-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" - integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== - dependencies: - commondir "^1.0.1" - make-dir "^2.0.0" - pkg-dir "^3.0.0" - -find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - -foreground-child@^1.5.6: - version "1.5.6" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-1.5.6.tgz#4fd71ad2dfde96789b980a5c0a295937cb2f5ce9" - integrity sha512-3TOY+4TKV0Ml83PXJQY+JFQaHNV38lzQDIzzXYg1kWdBLenGgoZhAs0CKgzI31vi2pWEpQMq/Yi4bpKwCPkw7g== - dependencies: - cross-spawn "^4" - signal-exit "^3.0.0" - forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -838,11 +699,6 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -get-caller-file@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - get-intrinsic@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" @@ -860,18 +716,6 @@ glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob@^7.1.3: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^8.0.0: version "8.1.0" resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" @@ -888,11 +732,6 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -915,13 +754,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hasha@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/hasha/-/hasha-3.0.0.tgz#52a32fab8569d41ca69a61ff1a214f8eb7c8bd39" - integrity sha512-w0Kz8lJFBoyaurBiNrIvxPqr/gJ6fOfSkpAPOepN3oECqGJag37xPbOv57izi/KP8auHgNYxn5fXtAb+1LsJ6w== - dependencies: - is-stream "^1.0.1" - help-me@^4.0.1: version "4.2.0" resolved "https://registry.yarnpkg.com/help-me/-/help-me-4.2.0.tgz#50712bfd799ff1854ae1d312c36eafcea85b0563" @@ -930,16 +762,6 @@ help-me@^4.0.1: glob "^8.0.0" readable-stream "^3.6.0" -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - http-errors@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" @@ -968,11 +790,6 @@ ignore-by-default@^1.0.1: resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -981,7 +798,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -991,11 +808,6 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -1003,23 +815,11 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-core-module@^2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" - integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== - dependencies: - has "^1.0.3" - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== - is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -1032,78 +832,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-stream@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - isomorphic.js@^0.2.4: version "0.2.5" resolved "https://registry.yarnpkg.com/isomorphic.js/-/isomorphic.js-0.2.5.tgz#13eecf36f2dba53e85d355e11bf9d4208c6f7f88" integrity sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw== -istanbul-lib-coverage@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" - integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== - -istanbul-lib-hook@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz#c95695f383d4f8f60df1f04252a9550e15b5b133" - integrity sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA== - dependencies: - append-transform "^1.0.0" - -istanbul-lib-instrument@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" - integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== - dependencies: - "@babel/generator" "^7.4.0" - "@babel/parser" "^7.4.3" - "@babel/template" "^7.4.0" - "@babel/traverse" "^7.4.3" - "@babel/types" "^7.4.0" - istanbul-lib-coverage "^2.0.5" - semver "^6.0.0" - -istanbul-lib-report@^2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" - integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== - dependencies: - istanbul-lib-coverage "^2.0.5" - make-dir "^2.1.0" - supports-color "^6.1.0" - -istanbul-lib-source-maps@^3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" - integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^2.0.5" - make-dir "^2.1.0" - rimraf "^2.6.3" - source-map "^0.6.1" - -istanbul-reports@^2.2.4: - version "2.2.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.7.tgz#5d939f6237d7b48393cc0959eab40cd4fd056931" - integrity sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg== - dependencies: - html-escaper "^2.0.0" - joycon@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" @@ -1114,24 +847,11 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -json-parse-better-errors@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - lib0@^0.2.74, lib0@^0.2.85: version "0.2.86" resolved "https://registry.yarnpkg.com/lib0/-/lib0-0.2.86.tgz#86ccc3366ca3b6f8b5d8713c03f2a67ed7bc5f3e" @@ -1139,42 +859,11 @@ lib0@^0.2.74, lib0@^0.2.85: dependencies: isomorphic.js "^0.2.4" -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== - dependencies: - p-locate "^3.0.0" - path-exists "^3.0.0" - -lodash.flattendeep@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" - integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== - lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -lru-cache@^4.0.1: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -1182,14 +871,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -make-dir@^2.0.0, make-dir@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" - integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== - dependencies: - pify "^4.0.1" - semver "^5.6.0" - make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" @@ -1210,13 +891,6 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== -merge-source-map@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646" - integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw== - dependencies: - source-map "^0.6.1" - methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -1239,7 +913,7 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -1258,47 +932,22 @@ minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -mkdirp@^0.5.0: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -mongodb@^3.3.2, mongodb@^3.7.4: - version "3.7.4" - resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.7.4.tgz#119530d826361c3e12ac409b769796d6977037a4" - integrity sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw== - dependencies: - bl "^2.2.1" - bson "^1.1.4" - denque "^1.4.1" - optional-require "^1.1.8" - safe-buffer "^5.1.2" - optionalDependencies: - saslprep "^1.0.0" - -mongoist@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/mongoist/-/mongoist-2.7.0.tgz#047544a5ddec30254a8df48e869f03790d01ab04" - integrity sha512-5OKz4esuw5sVHb2TRxX5dTKVgBqj/SlRvzPABhJNoTtTwqySM7fr3VWizbIlBF40rlQi2Mz68UmSpAtSb/7cfA== +mongodb-connection-string-url@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz#b4f87f92fd8593f3b9365f592515a06d304a1e9c" + integrity sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ== dependencies: - debug "^4.3.4" - mongodb "^3.7.4" + "@types/whatwg-url" "^11.0.2" + whatwg-url "^13.0.0" -mongojs@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mongojs/-/mongojs-3.1.0.tgz#4242e6f5218a7301c35393b64ba9130d1d9488ef" - integrity sha512-aXJ4xfXwx9s1cqtKTZ24PypXiWhIgvgENObQzCGbV4QBxEVedy3yuErhx6znk959cF2dOzL2ClgXJvIhfgkpIQ== +mongodb@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.3.0.tgz#ec9993b19f7ed2ea715b903fcac6171c9d1d38ca" + integrity sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA== dependencies: - each-series "^1.0.0" - mongodb "^3.3.2" - nyc "^14.1.1" - once "^1.4.0" - parse-mongo-url "^1.1.1" - readable-stream "^3.4.0" - thunky "^1.1.0" - to-mongodb-core "^2.0.0" + "@mongodb-js/saslprep" "^1.1.0" + bson "^6.2.0" + mongodb-connection-string-url "^3.0.0" ms@2.0.0: version "2.0.0" @@ -1320,11 +969,6 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== -nested-error-stacks@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz#26c8a3cee6cc05fbcf1e333cd2fc3e003326c0b5" - integrity sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw== - nodemon@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.0.1.tgz#affe822a2c5f21354466b2fc8ae83277d27dadc7" @@ -1348,52 +992,11 @@ nopt@~1.0.10: dependencies: abbrev "1" -normalize-package-data@^2.3.2: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -nyc@^14.1.1: - version "14.1.1" - resolved "https://registry.yarnpkg.com/nyc/-/nyc-14.1.1.tgz#151d64a6a9f9f5908a1b73233931e4a0a3075eeb" - integrity sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw== - dependencies: - archy "^1.0.0" - caching-transform "^3.0.2" - convert-source-map "^1.6.0" - cp-file "^6.2.0" - find-cache-dir "^2.1.0" - find-up "^3.0.0" - foreground-child "^1.5.6" - glob "^7.1.3" - istanbul-lib-coverage "^2.0.5" - istanbul-lib-hook "^2.0.7" - istanbul-lib-instrument "^3.3.0" - istanbul-lib-report "^2.0.8" - istanbul-lib-source-maps "^3.0.6" - istanbul-reports "^2.2.4" - js-yaml "^3.13.1" - make-dir "^2.1.0" - merge-source-map "^1.1.0" - resolve-from "^4.0.0" - rimraf "^2.6.3" - signal-exit "^3.0.2" - spawn-wrap "^1.4.2" - test-exclude "^5.2.3" - uuid "^3.3.2" - yargs "^13.2.2" - yargs-parser "^13.0.0" - object-inspect@^1.9.0: version "1.12.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" @@ -1418,107 +1021,21 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -optional-require@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/optional-require/-/optional-require-1.1.8.tgz#16364d76261b75d964c482b2406cb824d8ec44b7" - integrity sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA== - dependencies: - require-at "^1.0.6" - -os-homedir@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== - -p-limit@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== - dependencies: - p-limit "^2.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -package-hash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-3.0.0.tgz#50183f2d36c9e3e528ea0a8605dff57ce976f88e" - integrity sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA== - dependencies: - graceful-fs "^4.1.15" - hasha "^3.0.0" - lodash.flattendeep "^4.4.0" - release-zalgo "^1.0.0" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -parse-mongo-url@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/parse-mongo-url/-/parse-mongo-url-1.1.1.tgz#66238df5f8e7c0c8ca4cd970d4ab6a1373eb75b5" - integrity sha512-7bZUusQIrFLwvsLHBnCz2WKYQ5LKO/LwKPnvQxbMIh9gDx8H5ZsknRmLjZdn6GVdrgVOwqDrZKsY0qDLNmRgcw== - parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - picomatch@^2.0.4, picomatch@^2.2.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== - -pify@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" - integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== - pino-abstract-transport@^1.0.0, pino-abstract-transport@v1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz#083d98f966262164504afb989bccd05f665937a8" @@ -1569,13 +1086,6 @@ pino@^8.15.1: sonic-boom "^3.1.0" thread-stream "^2.0.0" -pkg-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" - integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== - dependencies: - find-up "^3.0.0" - prettier-plugin-organize-imports@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.3.tgz#6b0141ac71f7ee9a673ce83e95456319e3a7cf0d" @@ -1586,11 +1096,6 @@ prettier@^3.0.3: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - process-warning@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.2.0.tgz#008ec76b579820a8e5c35d81960525ca64feb626" @@ -1609,11 +1114,6 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== - pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" @@ -1627,6 +1127,11 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" +punycode@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + qs@6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -1663,37 +1168,7 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" -read-pkg-up@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" - integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== - dependencies: - find-up "^3.0.0" - read-pkg "^3.0.0" - -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - integrity sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA== - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - -readable-stream@^2.3.5: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -1725,59 +1200,11 @@ real-require@^0.2.0: resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== -release-zalgo@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" - integrity sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA== - dependencies: - es6-error "^4.0.1" - -require-at@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/require-at/-/require-at-1.0.6.tgz#9eb7e3c5e00727f5a4744070a7f560d4de4f6e6a" - integrity sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve@^1.10.0: - version "1.22.6" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" - integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -rimraf@^2.6.2, rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - safe-stable-stringify@^2.3.1: version "2.4.3" resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" @@ -1788,28 +1215,11 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -saslprep@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" - integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== - dependencies: - sparse-bitfield "^3.0.3" - secure-json-parse@^2.4.0: version "2.7.0" resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862" integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== -"semver@2 || 3 || 4 || 5", semver@^5.6.0: - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - -semver@^6.0.0: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - semver@^7.5.3: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" @@ -1846,11 +1256,6 @@ serve-static@1.15.0: parseurl "~1.3.3" send "0.18.0" -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -1865,11 +1270,6 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - simple-update-notifier@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" @@ -1884,11 +1284,6 @@ sonic-boom@^3.0.0, sonic-boom@^3.1.0: dependencies: atomic-sleep "^1.0.0" -source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - sparse-bitfield@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" @@ -1896,44 +1291,6 @@ sparse-bitfield@^3.0.3: dependencies: memory-pager "^1.0.2" -spawn-wrap@^1.4.2: - version "1.4.3" - resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.4.3.tgz#81b7670e170cca247d80bf5faf0cfb713bdcf848" - integrity sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw== - dependencies: - foreground-child "^1.5.6" - mkdirp "^0.5.0" - os-homedir "^1.0.1" - rimraf "^2.6.2" - signal-exit "^3.0.2" - which "^1.3.0" - -spdx-correct@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" - integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.15" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.15.tgz#142460aabaca062bc7cd4cc87b7d50725ed6a4ba" - integrity sha512-lpT8hSQp9jAKp9mhtBU4Xjon8LPGBvLIuBiSVhMEtmLecTh2mO0tlqrAMp47tBXzMr13NJMQ2lf7RpQGLJ3HsQ== - split-on-first@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-3.0.0.tgz#f04959c9ea8101b9b0bbf35a61b9ebea784a23e7" @@ -1944,25 +1301,11 @@ split2@^4.0.0: resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== - statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -1970,25 +1313,6 @@ string_decoder@^1.1.1, string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -2001,28 +1325,6 @@ supports-color@^5.3.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" -supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -test-exclude@^5.2.3: - version "5.2.3" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" - integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== - dependencies: - glob "^7.1.3" - minimatch "^3.0.4" - read-pkg-up "^4.0.0" - require-main-filename "^2.0.0" - thread-stream@^2.0.0: version "2.4.0" resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.4.0.tgz#5def29598d1d4171ba3bace7e023a71d87d99c07" @@ -2030,21 +1332,11 @@ thread-stream@^2.0.0: dependencies: real-require "^0.2.0" -thunky@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== -to-mongodb-core@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-mongodb-core/-/to-mongodb-core-2.0.0.tgz#3596ec7613ac9ad3b98a89dcb9aefba569cd27eb" - integrity sha512-vfXXcGYFP8+0L5IPOtUzzVIvPE/G3GN0TKa/PRBlzPqYyhm+UxhPmvv634EQgO4Ot8dHbBFihOslMJQclY8Z9A== - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -2064,6 +1356,13 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" +tr46@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" + integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw== + dependencies: + punycode "^2.3.0" + ts-node@^10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -2106,7 +1405,7 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -2116,64 +1415,34 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== -which-module@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" - integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== - -which@^1.2.9, which@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== +whatwg-url@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-13.0.0.tgz#b7b536aca48306394a34e44bda8e99f332410f8f" + integrity sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig== dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" + tr46 "^4.1.1" + webidl-conversions "^7.0.0" wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^2.4.2: - version "2.4.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" - integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - signal-exit "^3.0.2" - ws@^8.14.2: version "8.14.2" resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" @@ -2186,45 +1455,11 @@ y-protocols@^1.0.6: dependencies: lib0 "^0.2.85" -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^13.0.0, yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs@^13.2.2: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - yjs@^13.6.8: version "13.6.8" resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.6.8.tgz#0d6cebf4d7e69b08ede5ecf6368ddbd9c7603c2e" diff --git a/colab-api/src/main/resources/META-INF/dbchangelogs/1704643506.xml b/colab-api/src/main/resources/META-INF/dbchangelogs/1704643506.xml new file mode 100644 index 00000000000..2361fd086ad --- /dev/null +++ b/colab-api/src/main/resources/META-INF/dbchangelogs/1704643506.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/colab-api/src/main/resources/META-INF/dbchangelogs/1706694733.xml b/colab-api/src/main/resources/META-INF/dbchangelogs/1706694733.xml new file mode 100644 index 00000000000..6759f6b49c2 --- /dev/null +++ b/colab-api/src/main/resources/META-INF/dbchangelogs/1706694733.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/colab-tests/src/test/java/ch/colabproject/colab/tests/rest/SecurityTest.java b/colab-tests/src/test/java/ch/colabproject/colab/tests/rest/SecurityTest.java index 14581efa931..df3c33e7e1e 100644 --- a/colab-tests/src/test/java/ch/colabproject/colab/tests/rest/SecurityTest.java +++ b/colab-tests/src/test/java/ch/colabproject/colab/tests/rest/SecurityTest.java @@ -78,6 +78,9 @@ public void assertAccessDenied() { TestHelper.assertThrows(HttpErrorMessage.MessageCode.ACCESS_DENIED, () -> { client.presenceRestEndpoint.clearAllPresenceList(); }); + TestHelper.assertThrows(HttpErrorMessage.MessageCode.ACCESS_DENIED, () -> { + client.cronJobLogRestEndpoint.getAllCronJobLogs(); + }); } /** diff --git a/colab-webapp/default_colab.properties b/colab-webapp/default_colab.properties index 7a7e5e968ec..f2ff48a08c1 100644 --- a/colab-webapp/default_colab.properties +++ b/colab-webapp/default_colab.properties @@ -25,7 +25,7 @@ colab.smtp.auth=false colab.smtp.username= colab.smtp.password= colab.smtp.host=localhost -colab.smtp.port=25 +# colab.smtp.port=25 colab.smtp.starttls=false # Mailhog @@ -37,6 +37,8 @@ colab.smtp.port=1025 # Account Management Properties ############################### colab.localaccount.showcreatebutton=true +# Last Terms of Use and Data Policy release date in yyyy-MM-dd format +colab.termsofuse.date=2023-11-27 # Mongo DB (JCR) # docker run -d --restart always -p 27017:27017 --name colab_mongo mongo:4.4 diff --git a/colab-webapp/src/main/node/app/index.html b/colab-webapp/src/main/node/app/index.html deleted file mode 100644 index d9df3e01d73..00000000000 --- a/colab-webapp/src/main/node/app/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - co.LAB - - - - - -
- - - diff --git a/colab-webapp/src/main/node/app/src/API/api.ts b/colab-webapp/src/main/node/app/src/API/api.ts index ca733ad3e9e..9bb4d76f96d 100644 --- a/colab-webapp/src/main/node/app/src/API/api.ts +++ b/colab-webapp/src/main/node/app/src/API/api.ts @@ -27,8 +27,8 @@ import { GridPosition, HierarchicalPosition, HttpSession, - InvolvementLevel, InstanceMaker, + InvolvementLevel, Project, ProjectCreationData, ProjectStructure, @@ -36,8 +36,6 @@ import { ResourceCreationData, ResourceRef, SignUpInfo, - StickyNoteLink, - StickyNoteLinkCreationData, TeamMember, TeamRole, TouchUserPresence, @@ -47,6 +45,7 @@ import { WsSessionIdentifier, WsSignOutMessage, entityIs, + CronJobLog, } from 'colab-rest-client'; import { hashPassword } from '../SecurityHelper'; import { PasswordScore } from '../components/common/element/Form'; @@ -168,6 +167,13 @@ export const getLiveMonitoringData = createAsyncThunk( }, ); +export const getCronJobLogs = createAsyncThunk( + 'cronJobLogs/getAll', + async () => { + return await restClient.CronJobLogRestEndpoint.getAllCronJobLogs(); + }, +); + //////////////////////////////////////////////////////////////////////////////////////////////////// // Authentication //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -284,10 +290,10 @@ export const closeCurrentSession = createAsyncThunk( }, ); -export const getTosAndDataPolicyTime = createAsyncThunk( - 'security/getTosAndDataPolicyTime', +export const getTermsOfUseTime = createAsyncThunk( + 'security/getTermsOfUseTime', async () => { - return await restClient.SecurityRestEndPoint.getTosAndDataPolicyTimeEpoch(); + return await restClient.SecurityRestEndPoint.getTermsOfUseTimeEpoch(); }, ); @@ -302,26 +308,26 @@ export const reloadCurrentUser = createAsyncThunk('auth/reload', async (_noArg: const currentAccount = await restClient.UserRestEndpoint.getCurrentAccount(); const currentUser = await restClient.UserRestEndpoint.getCurrentUser(); - const tosAndDataPolicyTime = await restClient.SecurityRestEndPoint.getTosAndDataPolicyTimeEpoch(); + const termsOfUseTime = await restClient.SecurityRestEndPoint.getTermsOfUseTimeEpoch(); const allAccounts = await restClient.UserRestEndpoint.getAllCurrentUserAccounts(); const userAgreedTimestamp = new Date(currentUser?.agreedTime ?? 0); - // We create a unix time and set it with the policy time - const toSAndDataPolicyTimestamp = new Date(0); - toSAndDataPolicyTimestamp.setUTCSeconds(tosAndDataPolicyTime); + // We create a unix time and set it with the terms of use timestamp + const termsOfUseTimestamp = new Date(0); + termsOfUseTimestamp.setUTCMilliseconds(termsOfUseTime); const isUserAgreedTimeValid = currentUser && currentUser.agreedTime != null - ? userAgreedTimestamp > toSAndDataPolicyTimestamp + ? userAgreedTimestamp > termsOfUseTimestamp : false; if (isUserAgreedTimeValid) { // current user is authenticated const state = thunkApi.getState() as ColabState; - if (state.websockets.sessionId != null && state.auth.currentUserId != currentUser.id) { - // Websocket session is ready AND currentUser just changed + if (state.websockets.sessionId != null) { + // Websocket session is ready // reconnect to broadcast channel // subscribe to the new current user channel ASAP await restClient.WebsocketRestEndpoint.subscribeToBroadcastChannel({ @@ -334,6 +340,7 @@ export const reloadCurrentUser = createAsyncThunk('auth/reload', async (_noArg: }); } } + return { currentUser: currentUser, currentAccount: currentAccount, accounts: allAccounts }; }); @@ -1695,49 +1702,6 @@ export const deletePendingChanges = createAsyncThunk('block/deleteChanges', asyn return await restClient.ChangeRestEndpoint.deletePendingChanges(id); }); -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Sticky Note Links -//////////////////////////////////////////////////////////////////////////////////////////////////// - -//export const getStickyNoteLink = createAsyncThunk('stickyNoteLinks/get', async (id: number) => { -// return await restClient.StickyNoteLinkRestEndpoint.getLink(id); -//}); - -// TODO see if it belongs to stickyNoteLinks or to cards. Make your choice ! -export const getStickyNoteLinkAsDest = createAsyncThunk( - 'stickyNoteLinks/getAsDest', - async (cardId: number) => { - if (cardId > 0) { - return await restClient.CardRestEndpoint.getStickyNoteLinksAsDest(cardId); - } else { - return []; - } - }, -); - -export const createStickyNote = createAsyncThunk( - 'stickyNoteLinks/create', - async (stickyNote: StickyNoteLinkCreationData) => { - return await restClient.StickyNoteLinkRestEndpoint.createLink(stickyNote); - }, -); - -export const updateStickyNote = createAsyncThunk( - 'stickyNoteLinks/update', - async (stickyNote: StickyNoteLink) => { - return await restClient.StickyNoteLinkRestEndpoint.updateLink(stickyNote); - }, -); - -export const deleteStickyNote = createAsyncThunk( - 'stickyNoteLinks/delete', - async (stickyNote: StickyNoteLink) => { - if (stickyNote.id != null) { - return await restClient.StickyNoteLinkRestEndpoint.deleteLink(stickyNote.id); - } - }, -); - //////////////////////////////////////////////////////////////////////////////////////////////////// // Activity-Flow Links //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/colab-webapp/src/main/node/app/src/SecurityHelper.ts b/colab-webapp/src/main/node/app/src/SecurityHelper.ts index c7eb832af1d..adf6e00ea92 100644 --- a/colab-webapp/src/main/node/app/src/SecurityHelper.ts +++ b/colab-webapp/src/main/node/app/src/SecurityHelper.ts @@ -7,21 +7,15 @@ import * as API from 'colab-rest-client'; -function hexToUint8Array(hex: string): Uint8Array { - const bytes = new Uint8Array(hex.length / 2); - - let currentByte = 0; - for (let currentHexChar = 0; currentHexChar < hex.length; currentHexChar += 2) { - bytes[currentByte] = Number.parseInt(hex.substring(currentHexChar, currentHexChar + 2), 16); - currentByte++; +export async function hashPassword( + method: API.AuthMethod['mandatoryMethod'], + salt: string, + password: string, +): Promise { + switch (method) { + case 'PBKDF2WithHmacSHA512_65536_64': + return hashPBKDF2(salt, password, 'SHA-512', 65536, 64 * 8); } - return bytes; -} - -function bytesToHex(bytes: ArrayBuffer): string { - return Array.from(new Uint8Array(bytes)) - .map(byte => byte.toString(16).padStart(2, '0')) - .join(''); } export async function hashPBKDF2( @@ -53,13 +47,19 @@ export async function hashPBKDF2( return bytesToHex(keyBuffer); } -export async function hashPassword( - method: API.AuthMethod['mandatoryMethod'], - salt: string, - password: string, -): Promise { - switch (method) { - case 'PBKDF2WithHmacSHA512_65536_64': - return hashPBKDF2(salt, password, 'SHA-512', 65536, 64 * 8); +function hexToUint8Array(hex: string): Uint8Array { + const bytes = new Uint8Array(hex.length / 2); + + let currentByte = 0; + for (let currentHexChar = 0; currentHexChar < hex.length; currentHexChar += 2) { + bytes[currentByte] = Number.parseInt(hex.substring(currentHexChar, currentHexChar + 2), 16); + currentByte++; } + return bytes; +} + +function bytesToHex(bytes: ArrayBuffer): string { + return Array.from(new Uint8Array(bytes)) + .map(byte => byte.toString(16).padStart(2, '0')) + .join(''); } diff --git a/colab-webapp/src/main/node/app/src/components/AboutColab.tsx b/colab-webapp/src/main/node/app/src/components/AboutColab.tsx index fc7cc61dfd4..231f8ebfc46 100644 --- a/colab-webapp/src/main/node/app/src/components/AboutColab.tsx +++ b/colab-webapp/src/main/node/app/src/components/AboutColab.tsx @@ -6,31 +6,23 @@ */ import { css } from '@emotion/css'; -import React from 'react'; -import { useNavigate } from 'react-router-dom'; +import * as React from 'react'; import useTranslations from '../i18n/I18nContext'; import { useVersionDetails } from '../store/selectors/configSelector'; import Logo from '../styling/Logo'; import { space_lg } from '../styling/style'; -import IconButton from './common/element/IconButton'; import Flex from './common/layout/Flex'; +/** + * Some informations about the co.LAB platform and the co.LAB project + */ export default function AboutColab(): JSX.Element { const i18n = useTranslations(); - const navigate = useNavigate(); const version = useVersionDetails(); return ( - { - navigate(-1); - }} - className={css({ alignSelf: 'flex-start' })} - /> (); - - return ; -} - function App(): JSX.Element { const defaultLanguage = + // try to know it from navigator (navigator.languages .map(l => { // remove variant part and turn uppercase @@ -88,42 +85,44 @@ function App(): JSX.Element { }) .find(lang => { return languages.includes(lang as Language); - }) as Language) || 'EN'; + }) as Language) || + // else english + 'EN'; const [lang, setLang] = useLocalStorage('colab-language', defaultLanguage); const [tipsConfig, setTipsConfig] = useLocalStorage('colab-tips-config', { - TODO: false, - NEWS: true, TIPS: true, + NEWS: true, + FEATURE_PREVIEW: false, WIP: false, + TODO: false, DEBUG: false, - FEATURE_PREVIEW: false, }); - const setTodoCb = React.useCallback( + const setTipsCb = React.useCallback( (v: boolean) => setTipsConfig(state => ({ ...state, - TODO: v, + TIPS: v, })), [setTipsConfig], ); - const setTipsCb = React.useCallback( + const setNewsCb = React.useCallback( (v: boolean) => setTipsConfig(state => ({ ...state, - TIPS: v, + NEWS: v, })), [setTipsConfig], ); - const setNewsCb = React.useCallback( + const setFeaturePreviewCb = React.useCallback( (v: boolean) => setTipsConfig(state => ({ ...state, - NEWS: v, + FEATURE_PREVIEW: v, })), [setTipsConfig], ); @@ -137,11 +136,11 @@ function App(): JSX.Element { [setTipsConfig], ); - const setFeaturePreviewCb = React.useCallback( + const setTodoCb = React.useCallback( (v: boolean) => setTipsConfig(state => ({ ...state, - FEATURE_PREVIEW: v, + TODO: v, })), [setTipsConfig], ); @@ -172,9 +171,9 @@ function App(): JSX.Element { > - }> + }> - + + {/* Payara is happy with hash router */} - } /> - } /> + } /> + } /> - + diff --git a/colab-webapp/src/main/node/app/src/components/DataPolicyEN.tsx b/colab-webapp/src/main/node/app/src/components/DataPolicyEN.tsx index 9c04fe84270..fb8aed57e3c 100644 --- a/colab-webapp/src/main/node/app/src/components/DataPolicyEN.tsx +++ b/colab-webapp/src/main/node/app/src/components/DataPolicyEN.tsx @@ -17,7 +17,7 @@ import Collapsible from './common/layout/Collapsible'; /********************************************************* * - * If modified, update TosAndDataPolicy.java timestamp! + * If modified, update colab.termsofuse.date in config ! * *********************************************************/ diff --git a/colab-webapp/src/main/node/app/src/components/MainApp.tsx b/colab-webapp/src/main/node/app/src/components/MainApp.tsx index 9f1d3aef39d..bda9613e370 100644 --- a/colab-webapp/src/main/node/app/src/components/MainApp.tsx +++ b/colab-webapp/src/main/node/app/src/components/MainApp.tsx @@ -4,110 +4,63 @@ * * Licensed under the MIT License */ + import { css } from '@emotion/css'; import * as React from 'react'; import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom'; import * as API from '../API/api'; import useTranslations from '../i18n/I18nContext'; -import { useAppDispatch, useAppSelector } from '../store/hooks'; -import { useColabConfig, useTosAndDataPolicyTime } from '../store/selectors/configSelector'; -import { useCurrentProject, useProject } from '../store/selectors/projectSelector'; +import { useAppDispatch } from '../store/hooks'; +import { useColabConfig, useTermsOfUseTime } from '../store/selectors/configSelector'; import { useCurrentUser } from '../store/selectors/userSelector'; +import { useSessionId } from '../store/selectors/websocketSelector'; import AboutColab from './AboutColab'; +import DataPolicyEN from './DataPolicyEN'; import MainNav from './MainNav'; -import Admin from './admin/Admin'; +import ReconnectingOverlay from './ReconnectingOverlay'; +import TermsOfUseEN from './TermsOfUseEN'; +import AdminTabs from './admin/AdminTabs'; import ResetPasswordForm from './authentication/ForgotPassword'; import ResetPasswordSent from './authentication/ResetPasswordSent'; import SignInForm from './authentication/SignIn'; import SignUpForm from './authentication/SignUp'; -import InlineLoading from './common/element/InlineLoading'; +import UpdateTermsOfUseForm from './authentication/UpdateTermsOfUse'; import Flex from './common/layout/Flex'; -import Icon from './common/layout/Icon'; import Loading from './common/layout/Loading'; import Overlay from './common/layout/Overlay'; +import NewProjectAccess from './projects/NewProjectAccess'; import { MyModels, MyProjects } from './projects/ProjectList'; import ProjectsBin from './projects/ProjectsBin'; -import Editor from './projects/edition/Editor'; +import EditorWrapper from './projects/edition/EditorWrapper'; import NewModelShared from './projects/models/NewModelShared'; -import Settings from './settings/Settings'; -import TermsOfUseEN from './TermsOfUseEN'; -import DataPolicyEN from './DataPolicyEN'; -import UpdateTosAndDataPolicyForm from './authentication/UpdateTosAndDataPolicy'; - -const EditorWrapper = () => { - const { id: sId } = useParams<'id'>(); +import SettingsTabs from './settings/SettingsTabs'; - const id = +sId!; - const i18n = useTranslations(); +export default function MainApp(): React.ReactElement { const dispatch = useAppDispatch(); - const { project, status } = useProject(+id!); - const { project: editedProject, status: editingStatus } = useCurrentProject(); - - const webSocketId = useAppSelector(state => state.websockets.sessionId); - const socketIdRef = React.useRef(undefined); - - React.useEffect(() => { - if (webSocketId && project != null) { - if (editingStatus === 'NOT_EDITING' || (editedProject != null && editedProject.id !== +id)) { - socketIdRef.current = webSocketId; - dispatch(API.startProjectEdition(project)); - } else if (editingStatus === 'READY') { - if (webSocketId !== socketIdRef.current) { - // ws reconnection occured => reconnect - socketIdRef.current = webSocketId; - dispatch(API.reconnectToProjectChannel(project)); - } - } - } - }, [dispatch, editingStatus, editedProject, project, id, webSocketId]); - - if (status === 'NOT_INITIALIZED' || status === 'LOADING') { - return ; - } else if (project == null || status === 'ERROR') { - return ( -
- - {i18n.modules.project.info.noProject} -
- ); - } else { - if (editingStatus === 'NOT_EDITING' || (editedProject != null && editedProject.id !== +id)) { - return ; - } else { - return ; - } - } -}; + const i18n = useTranslations(); -// A custom hook that builds on useLocation to parse -// the query string for you. -function useQuery() { - return new URLSearchParams(useLocation().search); -} + const socketId = useSessionId(); + const isReconnecting = socketId == null; -export default function MainApp(): JSX.Element { - const dispatch = useAppDispatch(); - const i18n = useTranslations(); useColabConfig(); - const TosAndDataPolicyTime = useTosAndDataPolicyTime(); - const { currentUser, status: currentUserStatus } = useCurrentUser(); + const TermsOfUseTime = useTermsOfUseTime(); - const socketId = useAppSelector(state => state.websockets.sessionId); + const { currentUser, status: currentUserStatus } = useCurrentUser(); //const { project: projectBeingEdited } = useProjectBeingEdited(); const isUserAgreedTimeValid = React.useMemo(() => { - if (currentUser && currentUser.agreedTime != null && TosAndDataPolicyTime != 'LOADING') { + if (currentUser && currentUser.agreedTime != null && TermsOfUseTime != 'LOADING') { const userAgreedTimestamp = new Date(currentUser.agreedTime); // We create a unix time and set it with the policy time - const toSAndDataPolicyTimestamp = new Date(0); - toSAndDataPolicyTimestamp.setUTCSeconds(TosAndDataPolicyTime); - return userAgreedTimestamp > toSAndDataPolicyTimestamp; + const termsOfUseTimestamp = new Date(0); + termsOfUseTimestamp.setUTCMilliseconds(TermsOfUseTime); + return userAgreedTimestamp > termsOfUseTimestamp; } else { return false; } - }, [TosAndDataPolicyTime, currentUser]); + }, [TermsOfUseTime, currentUser]); React.useEffect(() => { if (currentUserStatus == 'NOT_INITIALIZED') { @@ -116,65 +69,48 @@ export default function MainApp(): JSX.Element { } }, [currentUserStatus, dispatch]); - const reconnecting = socketId == null && ( - -
- - {i18n.authentication.info.reconnecting} -
-
- ); - const query = useQuery(); if (currentUserStatus === 'NOT_INITIALIZED') { return ; - } else if (currentUserStatus == 'LOADING') { + } + + if (currentUserStatus == 'LOADING') { return ; - } else if (currentUserStatus === 'NOT_AUTHENTICATED') { + } + + if (currentUserStatus === 'NOT_AUTHENTICATED') { return ( <> - } /> - } /> + } /> + } /> } /> - } /> + } /> } /> } /> } /> } /> - {reconnecting} + {isReconnecting && } ); } else if (currentUser != null) { - if (TosAndDataPolicyTime === 'LOADING') { + if (TermsOfUseTime === 'LOADING') { return ; } else if (!isUserAgreedTimeValid) { return ( <> - } /> + } /> } /> } /> } /> - {reconnecting} + {isReconnecting && } ); } @@ -182,7 +118,7 @@ export default function MainApp(): JSX.Element { return ( <> - } /> + } /> } /> - } /> } /> } /> - } /> - } /> + } /> + {currentUser.admin && } />} } /> - {/* } /> */} + {/* } /> */} } /> + {/* this path comes from the server side (InvitationToken.java) */} + } /> + {/* this path comes from the server side (ModelSharingToken.java) */} + } />
} /> - } /> + {/* this path comes from the server side (ResetLocalAccountPasswordToken.java) */} + } /> - {reconnecting} + {isReconnecting && } ); - } else { - return ( - - {i18n.activity.inconsistentState} - - ); } + + // should not happen + return ( + + {i18n.activity.inconsistentState} + + ); +} + +// /** +// * To read parameters from URL +// */ +function EditorRouting() { + const { projectId } = useParams<'projectId'>(); + + return ; +} + +// A custom hook that builds on useLocation to parse +// the query string for you. +function useQuery() { + return new URLSearchParams(useLocation().search); } diff --git a/colab-webapp/src/main/node/app/src/components/MainNav.tsx b/colab-webapp/src/main/node/app/src/components/MainNav.tsx index 4dfc093c89c..60de40f0ffa 100644 --- a/colab-webapp/src/main/node/app/src/components/MainNav.tsx +++ b/colab-webapp/src/main/node/app/src/components/MainNav.tsx @@ -6,7 +6,7 @@ */ import { css, cx } from '@emotion/css'; -import React from 'react'; +import * as React from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import * as API from '../API/api'; import useTranslations from '../i18n/I18nContext'; @@ -155,7 +155,7 @@ export function UserDropDown({ mode = 'DEFAULT' }: UserDropDownProps): JSX.Eleme value: 'settings', label: ( <> - {i18n.user.settings} + {i18n.user.label.settings} ), action: () => navigate('./settings'), @@ -195,7 +195,7 @@ export function UserDropDown({ mode = 'DEFAULT' }: UserDropDownProps): JSX.Eleme {i18n.common.about} ), - action: () => navigate('/about-colab'), + action: () => window.open(`#/about`, '_blank'), }, { value: 'logout', diff --git a/colab-webapp/src/main/node/app/src/components/ReconnectingOverlay.tsx b/colab-webapp/src/main/node/app/src/components/ReconnectingOverlay.tsx new file mode 100644 index 00000000000..7fda1cd35d5 --- /dev/null +++ b/colab-webapp/src/main/node/app/src/components/ReconnectingOverlay.tsx @@ -0,0 +1,37 @@ +/* + * The coLAB project + * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO + * + * Licensed under the MIT License + */ + +import { css } from '@emotion/css'; +import * as React from 'react'; +import useTranslations from '../i18n/I18nContext'; +import InlineLoading from './common/element/InlineLoading'; +import Overlay from './common/layout/Overlay'; + +export default function ReconnectingOverlay(): JSX.Element { + const i18n = useTranslations(); + + return ( + +
+ + {i18n.authentication.info.reconnecting} +
+
+ ); +} diff --git a/colab-webapp/src/main/node/app/src/components/TermsOfUseEN.tsx b/colab-webapp/src/main/node/app/src/components/TermsOfUseEN.tsx index b678d1f539d..04900aca81b 100644 --- a/colab-webapp/src/main/node/app/src/components/TermsOfUseEN.tsx +++ b/colab-webapp/src/main/node/app/src/components/TermsOfUseEN.tsx @@ -17,7 +17,7 @@ import Collapsible from './common/layout/Collapsible'; /********************************************************* * - * If modified, update TosAndDataPolicy.java timestamp! + * If modified, update colab.termsofuse.date in config ! * *********************************************************/ diff --git a/colab-webapp/src/main/node/app/src/components/admin/Admin.tsx b/colab-webapp/src/main/node/app/src/components/admin/AdminTabs.tsx similarity index 82% rename from colab-webapp/src/main/node/app/src/components/admin/Admin.tsx rename to colab-webapp/src/main/node/app/src/components/admin/AdminTabs.tsx index abe18832250..73978ea70dc 100644 --- a/colab-webapp/src/main/node/app/src/components/admin/Admin.tsx +++ b/colab-webapp/src/main/node/app/src/components/admin/AdminTabs.tsx @@ -20,8 +20,9 @@ import LiveMonitor from './LiveMonitor'; import LoggersConfig from './LoggersConfig'; import MainPanel from './MainPanel'; import Who from './Who'; +import CronJobMonitor from './CronJobMonitor'; -export default function Admin(): JSX.Element { +export default function AdminTabs(): JSX.Element { const i18n = useTranslations(); const navigate = useNavigate(); @@ -39,7 +40,7 @@ export default function Admin(): JSX.Element { icon={'arrow_back'} onClick={() => navigate('..')} className={lightIconButtonStyle} - > + />

Admin Page

@@ -47,24 +48,27 @@ export default function Admin(): JSX.Element { + + + + + + - - - - - - - + + + +
diff --git a/colab-webapp/src/main/node/app/src/components/admin/CronJobMonitor.tsx b/colab-webapp/src/main/node/app/src/components/admin/CronJobMonitor.tsx new file mode 100644 index 00000000000..686b4492044 --- /dev/null +++ b/colab-webapp/src/main/node/app/src/components/admin/CronJobMonitor.tsx @@ -0,0 +1,99 @@ +/* + * The coLAB project + * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO + * + * Licensed under the MIT License + */ + +import * as React from 'react'; +import { space_2xl, text_xs } from '../../styling/style'; +import { css } from '@emotion/css'; +import { useAppDispatch, useAppSelector } from '../../store/hooks'; +import * as API from '../../API/api'; +import IconButton from '../common/element/IconButton'; +import { CronJobLog } from 'colab-rest-client/dist/ColabClient'; +import AvailabilityStatusIndicator from '../common/element/AvailabilityStatusIndicator'; + +const tableStyle = css({ + textAlign: 'left', + borderCollapse: 'collapse', + 'td, th': { + padding: '10px', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + fontSize: text_xs, + }, +}); + +const headerStyle = css({ + fontWeight: 'bold', + height: space_2xl, +}); + +export default function CronJobMonitor(): React.ReactElement { + const dispatch = useAppDispatch(); + + const data = useAppSelector(state => state.admin.cronJobLogs); + + const sync = React.useCallback(() => { + dispatch(API.getCronJobLogs()); + }, [dispatch]); + + React.useEffect(() => { + if (data === 'NOT_INITIALIZED') { + sync(); + } + }, [data, sync]); + + return ( +
+

+ Cronjob Monitoring +

+
+ {typeof data === 'string' ? ( + + ) : data.length === 0 ? ( +
+ no cronjob logs available +
+ ) : ( + + + + + + + + + {data.map((cronJobLog: CronJobLog) => ( + + ))} + +
Job NameLast Run Time
+ )} +
+
+ ); +} + +function CronJobRow({ name, time }: { name: string; time: number }): React.ReactElement { + const cronJobTime = new Date(0); + cronJobTime.setUTCMilliseconds(time); + + return ( + + {name} + {cronJobTime.toLocaleString()} + + ); +} diff --git a/colab-webapp/src/main/node/app/src/components/admin/LiveMonitor.tsx b/colab-webapp/src/main/node/app/src/components/admin/LiveMonitor.tsx index a0d53016ee1..7a97a91ae03 100644 --- a/colab-webapp/src/main/node/app/src/components/admin/LiveMonitor.tsx +++ b/colab-webapp/src/main/node/app/src/components/admin/LiveMonitor.tsx @@ -10,6 +10,7 @@ import { BlockMonitoring } from 'colab-rest-client'; import * as React from 'react'; import * as API from '../../API/api'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; +import { putInBinDefaultIcon } from '../../styling/IconDefault'; import AvailabilityStatusIndicator from '../common/element/AvailabilityStatusIndicator'; import Button from '../common/element/Button'; import IconButton from '../common/element/IconButton'; @@ -59,7 +60,7 @@ function Grid({ data, sync }: { data: BlockMonitoring[]; sync: () => void }): JS {entry.status === 'DELETED' && ( + + + )} + > + {() => ( + + {user.admin + ? i18n.admin.action.revokeAdminRightTo + : i18n.admin.action.grantAdminRightTo}{' '} + {user.username} + + )} + -
- action.... +
+ } + showCloseButton + > + {() => } +
); @@ -62,6 +114,7 @@ interface UserListProps { const headerStyle = css({ fontWeight: 'bold', + height: space_2xl, borderBottom: '1px solid', }); @@ -90,30 +143,33 @@ const Header = ({ sortKey, text }: HeaderProps) => { const colour = sortKey === sortBy.key ? 'black' : 'lightgrey'; const icon = sortBy.direction > 0 || sortKey != sortBy.key ? 'sort_by_alpha' : 'sort_by_alpha'; return ( -
+ {text} -
+ ); } else { - return
{text}
; + return {text}; } }; const Headers = () => { + const i18n = useTranslations(); + return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
); }; @@ -177,8 +233,10 @@ export default function UserList({ users }: UserListProps): JSX.Element {
[] = [ { key: 'email', - label: i18n.authentication.field.emailAddress, + label: i18n.authentication.label.emailAddress, type: 'text', isMandatory: true, - isErroneous: value => value.email.match(emailFormat) == null, + isErroneous: value => !assertEmailFormat(value.email), errorMessage: i18n.authentication.error.emailAddressNotValid, }, ]; @@ -52,7 +52,7 @@ export default function ResetPasswordForm({ redirectTo }: ResetPasswordFormProps dispatch(API.requestPasswordReset(email)).then(action => { stopLoading(); if (action.meta.requestStatus === 'fulfilled') { - navigate('../ResetPasswordEmailSent'); + navigate('../password-change-sent'); } }); }, @@ -65,14 +65,14 @@ export default function ResetPasswordForm({ redirectTo }: ResetPasswordFormProps fields={formFields} value={defaultData} onSubmit={requestPasswordReset} - submitLabel={i18n.authentication.action.sendMePassword} + submitLabel={i18n.authentication.action.sendMeLinkToChangePassword} isSubmitInProcess={isLoading} className={css({ width: '250px' })} buttonClassName={css({ margin: space_lg + ' auto' })} > {i18n.common.cancel} diff --git a/colab-webapp/src/main/node/app/src/components/authentication/PublicEntranceContainer.tsx b/colab-webapp/src/main/node/app/src/components/authentication/PublicEntranceContainer.tsx index a5c951a513f..f91be32698e 100644 --- a/colab-webapp/src/main/node/app/src/components/authentication/PublicEntranceContainer.tsx +++ b/colab-webapp/src/main/node/app/src/components/authentication/PublicEntranceContainer.tsx @@ -7,11 +7,11 @@ import { css, cx } from '@emotion/css'; import * as React from 'react'; +import { Link } from 'react-router-dom'; import Logo from '../../styling/Logo'; +import { m_md } from '../../styling/style'; import Flex from '../common/layout/Flex'; import Monkeys from '../debugger/monkey/Monkeys'; -import { m_md } from '../../styling/style'; -import { Link } from 'react-router-dom'; interface PublicEntranceContainerProps { children: React.ReactNode; diff --git a/colab-webapp/src/main/node/app/src/components/authentication/SignIn.tsx b/colab-webapp/src/main/node/app/src/components/authentication/SignIn.tsx index 85d67df4c83..764d9ed8a13 100644 --- a/colab-webapp/src/main/node/app/src/components/authentication/SignIn.tsx +++ b/colab-webapp/src/main/node/app/src/components/authentication/SignIn.tsx @@ -23,7 +23,7 @@ import PublicEntranceContainer from './PublicEntranceContainer'; interface SignInFormProps { redirectTo: string | null; - message?: string | React.ReactNode; + messages?: string[]; forceShowCreateAccountButton?: boolean; } @@ -47,7 +47,7 @@ const defaultCredentials: Credentials = { export default function SignInForm({ redirectTo, - message, + messages, forceShowCreateAccountButton, }: SignInFormProps): JSX.Element { const dispatch = useAppDispatch(); @@ -63,13 +63,13 @@ export default function SignInForm({ const formFields: Field[] = [ { key: 'identifier', - label: i18n.authentication.field.emailOrUsername, + label: i18n.authentication.label.emailOrUsername, type: 'text', isMandatory: true, }, { key: 'password', - label: i18n.authentication.field.password, + label: i18n.authentication.label.password, type: 'password', isMandatory: true, showStrengthBar: false, @@ -116,7 +116,14 @@ export default function SignInForm({ return ( - {message && {message}} + {messages && + messages.map(message => { + return ( + + {message} + + ); + })}
{i18n.authentication.action.resetPassword} {(forceShowCreateAccountButton || accountConfig.showCreateAccountButton) && ( {i18n.authentication.action.createAnAccount} diff --git a/colab-webapp/src/main/node/app/src/components/authentication/SignUp.tsx b/colab-webapp/src/main/node/app/src/components/authentication/SignUp.tsx index eccc51d2882..9daac88c984 100644 --- a/colab-webapp/src/main/node/app/src/components/authentication/SignUp.tsx +++ b/colab-webapp/src/main/node/app/src/components/authentication/SignUp.tsx @@ -10,7 +10,7 @@ import { WithJsonDiscriminator } from 'colab-rest-client'; import * as React from 'react'; import { Link, useNavigate } from 'react-router-dom'; import * as API from '../../API/api'; -import { buildLinkWithQueryParam, emailFormat } from '../../helper'; +import { assertEmailFormat, assertUserNameFormat, buildLinkWithQueryParam } from '../../helper'; import useTranslations from '../../i18n/I18nContext'; import { useAppDispatch, useLoadingState } from '../../store/hooks'; import { lightLinkStyle, space_lg } from '../../styling/style'; @@ -66,17 +66,17 @@ export default function SignUpForm({ redirectTo }: SignUpFormProps): JSX.Element const formFields: Field[] = [ { key: 'email', - label: i18n.authentication.field.emailAddress, + label: i18n.authentication.label.emailAddress, type: 'text', isMandatory: true, autoComplete: 'off', - isErroneous: value => value.email.match(emailFormat) == null, + isErroneous: value => !assertEmailFormat(value.email), errorMessage: i18n.authentication.error.emailAddressNotValid, }, { key: 'password', - label: i18n.authentication.field.password, - placeholder: i18n.authentication.placeholder.min7Char, + label: i18n.authentication.label.password, + placeholder: i18n.authentication.info.min7Char, type: 'password', isMandatory: true, autoComplete: 'off', @@ -87,7 +87,7 @@ export default function SignUpForm({ redirectTo }: SignUpFormProps): JSX.Element }, { key: 'confirm', - label: i18n.authentication.field.passwordConfirmation, + label: i18n.authentication.label.passwordConfirmation, type: 'password', isMandatory: true, autoComplete: 'off', @@ -97,30 +97,30 @@ export default function SignUpForm({ redirectTo }: SignUpFormProps): JSX.Element }, { key: 'username', - label: i18n.authentication.field.username, + label: i18n.authentication.label.username, type: 'text', isMandatory: true, autoComplete: 'off', - isErroneous: value => value.username.match(/^[a-zA-Z0-9._-]+$/) == null, + isErroneous: value => !assertUserNameFormat(value.username), errorMessage: i18n.authentication.error.usernameNotValid, }, { key: 'firstname', - label: i18n.authentication.field.firstname, + label: i18n.authentication.label.firstname, type: 'text', isMandatory: true, autoComplete: 'off', }, { key: 'lastname', - label: i18n.authentication.field.lastname, + label: i18n.authentication.label.lastname, type: 'text', isMandatory: true, autoComplete: 'off', }, { key: 'affiliation', - label: i18n.authentication.field.affiliation, + label: i18n.authentication.label.affiliation, type: 'text', isMandatory: false, autoComplete: 'off', @@ -130,13 +130,13 @@ export default function SignUpForm({ redirectTo }: SignUpFormProps): JSX.Element key: 'agreed', label: ( - {i18n.authentication.field.iAccept + ' '} + {i18n.authentication.label.iAccept + ' '} e.stopPropagation()}> - {i18n.authentication.field.termOfUse} + {i18n.authentication.label.termOfUse} - {' ' + i18n.authentication.field.and + ' '} + {' ' + i18n.common.and + ' '} e.stopPropagation()}> - {i18n.authentication.field.dataPolicy} + {i18n.authentication.label.dataPolicy} ), @@ -144,7 +144,7 @@ export default function SignUpForm({ redirectTo }: SignUpFormProps): JSX.Element showAs: 'checkbox', isMandatory: true, isErroneous: data => !data.agreed, - errorMessage: i18n.authentication.field.notAgreed, + errorMessage: i18n.authentication.error.notAgreed, }, ]; @@ -199,7 +199,7 @@ export default function SignUpForm({ redirectTo }: SignUpFormProps): JSX.Element isSubmitInProcess={isLoading} > {i18n.common.cancel} diff --git a/colab-webapp/src/main/node/app/src/components/authentication/UpdateTosAndDataPolicy.tsx b/colab-webapp/src/main/node/app/src/components/authentication/UpdateTermsOfUse.tsx similarity index 86% rename from colab-webapp/src/main/node/app/src/components/authentication/UpdateTosAndDataPolicy.tsx rename to colab-webapp/src/main/node/app/src/components/authentication/UpdateTermsOfUse.tsx index f873ac0440b..4d2fc769cb8 100644 --- a/colab-webapp/src/main/node/app/src/components/authentication/UpdateTosAndDataPolicy.tsx +++ b/colab-webapp/src/main/node/app/src/components/authentication/UpdateTermsOfUse.tsx @@ -5,17 +5,17 @@ * Licensed under the MIT License */ -import { useCurrentUser } from '../../store/selectors/userSelector'; +import { css } from '@emotion/css'; import * as React from 'react'; +import { Link } from 'react-router-dom'; import * as API from '../../API/api'; -import { useAppDispatch, useLoadingState } from '../../store/hooks'; import useTranslations from '../../i18n/I18nContext'; -import Form, { Field } from '../common/element/Form'; -import { Link } from 'react-router-dom'; -import Flex from '../common/layout/Flex'; -import { css } from '@emotion/css'; +import { useAppDispatch, useLoadingState } from '../../store/hooks'; +import { useCurrentUser } from '../../store/selectors/userSelector'; import { space_lg, text_md } from '../../styling/style'; import { UserDropDown } from '../MainNav'; +import Form, { Field } from '../common/element/Form'; +import Flex from '../common/layout/Flex'; interface FormData { agreed: false; @@ -25,7 +25,7 @@ const defaultData: FormData = { agreed: false, }; -export default function UpdateTosAndDataPolicyForm() { +export default function UpdateTermsOfUseForm() { const dispatch = useAppDispatch(); const i18n = useTranslations(); @@ -59,17 +59,17 @@ export default function UpdateTosAndDataPolicyForm() { key: 'agreed', label: ( - {i18n.authentication.field.iAccept + ' '} - {i18n.authentication.field.termOfUse} - {' ' + i18n.authentication.field.and + ' '} - {i18n.authentication.field.dataPolicy} + {i18n.authentication.label.iAccept + ' '} + {i18n.authentication.label.termOfUse} + {' ' + i18n.common.and + ' '} + {i18n.authentication.label.dataPolicy} ), type: 'boolean', showAs: 'checkbox', isMandatory: true, isErroneous: data => !data.agreed, - errorMessage: i18n.authentication.field.notAgreed, + errorMessage: i18n.authentication.error.notAgreed, }, ]; @@ -84,12 +84,12 @@ export default function UpdateTosAndDataPolicyForm() { justify="center" className={css({ margin: 'auto', maxWidth: '500px' })} > -

{i18n.authentication.info.updatedToSAndDataPolicy}

+

{i18n.authentication.info.updatedTermsOfUse}

{ if (currentProjectId) { - navigate(`/editor/${currentProjectId}/docs/cardTypes`); + navigate(`/project/${currentProjectId}/docs/card-types`); } }} kind="outline" diff --git a/colab-webapp/src/main/node/app/src/components/cards/CardEditor.tsx b/colab-webapp/src/main/node/app/src/components/cards/CardEditor.tsx index 01a2d3e709b..a48509a4130 100644 --- a/colab-webapp/src/main/node/app/src/components/cards/CardEditor.tsx +++ b/colab-webapp/src/main/node/app/src/components/cards/CardEditor.tsx @@ -9,6 +9,7 @@ import { css } from '@emotion/css'; import { Card, CardContent } from 'colab-rest-client'; import * as React from 'react'; import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex'; +import 'react-reflex/styles.css'; import * as API from '../../API/api'; import useTranslations from '../../i18n/I18nContext'; import { useAppDispatch } from '../../store/hooks'; @@ -16,7 +17,7 @@ import { useCardACLForCurrentUser } from '../../store/selectors/aclSelector'; import { useAndLoadSubCards } from '../../store/selectors/cardSelector'; import { space_md, space_sm, space_xs } from '../../styling/style'; import { cardColors } from '../../styling/theme'; -import { ColorPicker } from '../common/element/ColorPicker'; +import { ColorPicker } from '../common/element/color/ColorPicker'; import Flex from '../common/layout/Flex'; import { Item, SideCollapsibleCtx } from '../common/layout/SideCollapsibleContext'; import { TextEditorContext } from '../documents/texteditor/TextEditorContext'; diff --git a/colab-webapp/src/main/node/app/src/components/cards/CardEditorHeader.tsx b/colab-webapp/src/main/node/app/src/components/cards/CardEditorHeader.tsx index 27c4a09c7b9..17785e3be0d 100644 --- a/colab-webapp/src/main/node/app/src/components/cards/CardEditorHeader.tsx +++ b/colab-webapp/src/main/node/app/src/components/cards/CardEditorHeader.tsx @@ -8,7 +8,6 @@ import { css } from '@emotion/css'; import { Card, CardContent, entityIs } from 'colab-rest-client'; import * as React from 'react'; -import 'react-reflex/styles.css'; import { useNavigate } from 'react-router-dom'; import * as API from '../../API/api'; import useTranslations from '../../i18n/I18nContext'; @@ -16,7 +15,7 @@ import { useAppDispatch } from '../../store/hooks'; import { useVariantsOrLoad } from '../../store/selectors/cardSelector'; import { useCurrentUser } from '../../store/selectors/userSelector'; import { addNotification } from '../../store/slice/notificationSlice'; -import { putInBinDefaultIcon } from '../../styling/IconDefault'; +import { dropDownMenuDefaultIcon, putInBinDefaultIcon } from '../../styling/IconDefault'; import { heading_sm, lightIconButtonStyle, @@ -201,7 +200,7 @@ export default function CardEditorHeader({ /> (); + const { cardId: id, vId } = useParams<'cardId' | 'vId'>(); const cardId = +id!; const cardContentId = +vId!; diff --git a/colab-webapp/src/main/node/app/src/components/cards/StatusDropDown.tsx b/colab-webapp/src/main/node/app/src/components/cards/StatusDropDown.tsx index 1fe326ca0f0..faac09cd370 100644 --- a/colab-webapp/src/main/node/app/src/components/cards/StatusDropDown.tsx +++ b/colab-webapp/src/main/node/app/src/components/cards/StatusDropDown.tsx @@ -7,7 +7,7 @@ import { css, cx } from '@emotion/css'; import { CardContent } from 'colab-rest-client'; -import React from 'react'; +import * as React from 'react'; import { iconButtonStyle, p_xs } from '../../styling/style'; import DropDownMenu, { Entry, entryStyle } from '../common/layout/DropDownMenu'; import { IconSize } from '../common/layout/Icon'; diff --git a/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeCreator.tsx b/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeCreator.tsx index be8d5a35799..3da243b23a0 100644 --- a/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeCreator.tsx +++ b/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeCreator.tsx @@ -17,7 +17,7 @@ import { import { useCurrentProjectId } from '../../store/selectors/projectSelector'; import { buttonStyle, space_lg } from '../../styling/style'; import Button from '../common/element/Button'; -import Form, { createSelectField, Field } from '../common/element/Form'; +import Form, { Field, createSelectField } from '../common/element/Form'; import Icon from '../common/layout/Icon'; import OpenModalOnClick from '../common/layout/OpenModalOnClick'; diff --git a/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeEditor.tsx b/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeEditor.tsx index cfc5a35e1f4..ba5be1a851a 100644 --- a/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeEditor.tsx +++ b/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeEditor.tsx @@ -18,6 +18,7 @@ import { useGlobalCardTypeTags, } from '../../store/selectors/cardTypeSelector'; import { useCurrentProjectId } from '../../store/selectors/projectSelector'; +import { putInBinDefaultIcon } from '../../styling/IconDefault'; import { cardStyle, space_lg, space_sm } from '../../styling/style'; import AvailabilityStatusIndicator from '../common/element/AvailabilityStatusIndicator'; import Button from '../common/element/Button'; @@ -206,7 +207,7 @@ export default function CardTypeEditor({ className, usage }: CardTypeEditorProps css({ color: 'var(--error-main)', borderColor: 'var(--error-main)' }), )} > - {i18n.common.delete} + {i18n.common.delete} } className={css({ diff --git a/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeItem.tsx b/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeItem.tsx index 5184e947bbc..dcfff188787 100644 --- a/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeItem.tsx +++ b/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeItem.tsx @@ -12,6 +12,7 @@ import * as API from '../../API/api'; import useTranslations from '../../i18n/I18nContext'; import { useAppDispatch } from '../../store/hooks'; import { useCurrentProjectId } from '../../store/selectors/projectSelector'; +import { dropDownMenuDefaultIcon, putInBinDefaultIcon } from '../../styling/IconDefault'; import { lightIconButtonStyle, space_lg, space_sm } from '../../styling/style'; import { CardTypeAllInOne as CardType } from '../../types/cardTypeDefinition'; import { ConfirmDeleteModal } from '../common/layout/ConfirmDeleteModal'; @@ -55,7 +56,7 @@ export default function CardTypeItem({ cardType, usage }: CardTypeItemProps): JS

{cardType.title || i18n.modules.cardType.titlePlaceholder}

{i18n.common.edit} ), - action: () => navigate(`./edit/${cardType.ownId}`), + action: () => navigate(`./card-type/${cardType.ownId}`), }, ] : []), @@ -121,7 +122,8 @@ export default function CardTypeItem({ cardType, usage }: CardTypeItemProps): JS value: 'delete', label: ( <> - {i18n.common.delete} + {' '} + {i18n.common.delete} ), action: () => setShowDelete(true), diff --git a/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeThumbnail.tsx b/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeThumbnail.tsx index dcf12f48bb5..2b99f38d135 100644 --- a/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeThumbnail.tsx +++ b/colab-webapp/src/main/node/app/src/components/cardtypes/CardTypeThumbnail.tsx @@ -9,11 +9,12 @@ import { css, cx } from '@emotion/css'; import * as React from 'react'; import { useNavigate } from 'react-router-dom'; import * as API from '../../API/api'; -import { checkUnreachable } from '../../helper'; +import { assertUnreachable } from '../../helper'; import useTranslations from '../../i18n/I18nContext'; import { useAppDispatch, useLoadingState } from '../../store/hooks'; import { useAllProjectCardTypes } from '../../store/selectors/cardSelector'; import { useCurrentProjectId } from '../../store/selectors/projectSelector'; +import { dropDownMenuDefaultIcon } from '../../styling/IconDefault'; import { lightIconButtonStyle, multiLineEllipsisStyle, @@ -101,7 +102,7 @@ export default function CardTypeThumbnail({
{editable && ( {i18n.common.edit} ), - action: () => navigate(`./edit/${cardType.ownId}`), + action: () => navigate(`./card-type/${cardType.ownId}`), }, ] : []), @@ -252,7 +253,7 @@ export default function CardTypeThumbnail({ return <>; } default: - checkUnreachable(showModal); + assertUnreachable(showModal); } })()} diff --git a/colab-webapp/src/main/node/app/src/components/cardtypes/GlobalCardTypeList.tsx b/colab-webapp/src/main/node/app/src/components/cardtypes/GlobalCardTypeList.tsx index d0ef93ca030..aeeeed59557 100644 --- a/colab-webapp/src/main/node/app/src/components/cardtypes/GlobalCardTypeList.tsx +++ b/colab-webapp/src/main/node/app/src/components/cardtypes/GlobalCardTypeList.tsx @@ -39,8 +39,8 @@ export default function GlobalCardTypeList(): JSX.Element { if (lastCreated) { cardTypes.forEach(cardType => { if (cardType.id === lastCreated) { - navigate(`./edit/${cardType.id}`); - navigate(`./edit/${cardType.ownId}`), setLastCreated(null); + navigate(`./card-type/${cardType.id}`); + navigate(`./card-type/${cardType.ownId}`), setLastCreated(null); } }); } @@ -48,9 +48,9 @@ export default function GlobalCardTypeList(): JSX.Element { return ( - } /> + } /> {/* TODO : stabilize the routes ! Now : easy path to make it work*/} - } /> + } /> { if (cardType.id === lastCreated) { - navigate(`./edit/${cardType.id}`); + navigate(`./card-type/${cardType.id}`); setLastCreated(null); } }); @@ -62,9 +62,12 @@ export default function ProjectCardTypeList(): JSX.Element { return ( - } /> + } /> {/* TODO : stabilize the routes ! Now : easy path to make it work*/} - } /> + } + /> { if (item) { - navigate(`./edit/${item.ownId}`); + navigate(`./card-type/${item.ownId}`); } }} customThumbnailStyle={cx(cardTypeThumbnailStyle, customThumbStyle)} diff --git a/colab-webapp/src/main/node/app/src/components/cardtypes/summary/TargetCardTypeSummary.tsx b/colab-webapp/src/main/node/app/src/components/cardtypes/summary/TargetCardTypeSummary.tsx index 87c9604d046..9f55dc2dd83 100644 --- a/colab-webapp/src/main/node/app/src/components/cardtypes/summary/TargetCardTypeSummary.tsx +++ b/colab-webapp/src/main/node/app/src/components/cardtypes/summary/TargetCardTypeSummary.tsx @@ -10,10 +10,10 @@ import { entityIs } from 'colab-rest-client'; import * as React from 'react'; import useTranslations from '../../../i18n/I18nContext'; import { useProject } from '../../../store/selectors/projectSelector'; +import { MaterialIconsType } from '../../../styling/IconType'; import { space_sm } from '../../../styling/style'; import { CardTypeAllInOne as CardType } from '../../../types/cardTypeDefinition'; import Icon from '../../common/layout/Icon'; -import { MaterialIconsType } from '../../../styling/IconType'; export const referenceIcon = 'star'; diff --git a/colab-webapp/src/main/node/app/src/components/common/collection/FilterableList.tsx b/colab-webapp/src/main/node/app/src/components/common/collection/FilterableList.tsx index 250849c6b07..e45227df55f 100644 --- a/colab-webapp/src/main/node/app/src/components/common/collection/FilterableList.tsx +++ b/colab-webapp/src/main/node/app/src/components/common/collection/FilterableList.tsx @@ -6,7 +6,7 @@ */ import { css, cx } from '@emotion/css'; -import React from 'react'; +import * as React from 'react'; import useTranslations from '../../../i18n/I18nContext'; import { lightIconButtonStyle, diff --git a/colab-webapp/src/main/node/app/src/components/common/collection/Filters.tsx b/colab-webapp/src/main/node/app/src/components/common/collection/Filters.tsx index 53b0fde72dc..7f6cc1bd797 100644 --- a/colab-webapp/src/main/node/app/src/components/common/collection/Filters.tsx +++ b/colab-webapp/src/main/node/app/src/components/common/collection/Filters.tsx @@ -6,7 +6,6 @@ */ import * as React from 'react'; -import { ReactNode } from 'react'; export interface IFilter { property: keyof T; @@ -50,7 +49,7 @@ export function Filters(props: IFiltersProps) { !Array.isArray(object) && object !== null && Object.keys(object).map(key => { - const getRadioButton = (isTruthyPicked: boolean): ReactNode => { + const getRadioButton = (isTruthyPicked: boolean): React.ReactNode => { const id = isTruthyPicked ? `radio-defined-${key}` : `radio-not-defined-${key}`; const label = isTruthyPicked ? labelTruthy : labelFalsy; diff --git a/colab-webapp/src/main/node/app/src/components/common/collection/SearchInput.tsx b/colab-webapp/src/main/node/app/src/components/common/collection/SearchInput.tsx index 82175e04800..4b3e6d4d133 100644 --- a/colab-webapp/src/main/node/app/src/components/common/collection/SearchInput.tsx +++ b/colab-webapp/src/main/node/app/src/components/common/collection/SearchInput.tsx @@ -6,14 +6,13 @@ */ import * as React from 'react'; -import { useEffect, useState } from 'react'; import Flex from '../layout/Flex'; import Icon from '../layout/Icon'; function useDebounce(value: string | undefined, delay: number) { - const [debouncedValue, setDebouncedValue] = useState(value); + const [debouncedValue, setDebouncedValue] = React.useState(value); - useEffect(() => { + React.useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); @@ -30,11 +29,11 @@ export interface ISearchProps { } export default function SearchInput(props: ISearchProps) { - const [searchQuery, setSearchQuery] = useState(); + const [searchQuery, setSearchQuery] = React.useState(); const { onChangeSearchQuery } = props; const debouncedSearchQuery = useDebounce(searchQuery, 250); - useEffect(() => { + React.useEffect(() => { if (debouncedSearchQuery !== undefined) { onChangeSearchQuery(debouncedSearchQuery); } diff --git a/colab-webapp/src/main/node/app/src/components/common/collection/SearchSortList.tsx b/colab-webapp/src/main/node/app/src/components/common/collection/SearchSortList.tsx index 17139d3fb55..087860f70d9 100644 --- a/colab-webapp/src/main/node/app/src/components/common/collection/SearchSortList.tsx +++ b/colab-webapp/src/main/node/app/src/components/common/collection/SearchSortList.tsx @@ -6,7 +6,7 @@ */ import { css } from '@emotion/css'; -import React, { useState } from 'react'; +import * as React from 'react'; import { lightTextStyle, space_sm } from '../../../styling/style'; import Flex from '../layout/Flex'; import { Filters, IFilter, genericFilter } from './Filters'; @@ -59,12 +59,12 @@ interface SearchSortListProps { itemComp: (item: IWidget) => React.ReactNode; } export default function SearchSortList({ widgets, itemComp }: SearchSortListProps): JSX.Element { - const [query, setQuery] = useState(''); - const [activeSorter, setActiveSorter] = useState>({ + const [query, setQuery] = React.useState(''); + const [activeSorter, setActiveSorter] = React.useState>({ property: 'title', isDescending: true, }); - const [activeFilters, setActiveFilters] = useState>>([]); + const [activeFilters, setActiveFilters] = React.useState>>([]); const resultWidgets = widgets .filter((widget: IWidget) => genericSearch(widget, ['title', 'description'], query)) diff --git a/colab-webapp/src/main/node/app/src/components/common/element/AccessDenied.tsx b/colab-webapp/src/main/node/app/src/components/common/element/AccessDenied.tsx new file mode 100644 index 00000000000..7117246ffde --- /dev/null +++ b/colab-webapp/src/main/node/app/src/components/common/element/AccessDenied.tsx @@ -0,0 +1,15 @@ +/* + * The coLAB project + * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO + * + * Licensed under the MIT License + */ + +import * as React from 'react'; +import useTranslations from '../../../i18n/I18nContext'; + +export default function AccessDenied(): JSX.Element { + const i18n = useTranslations(); + + return {i18n.common.error.accessDenied}; +} diff --git a/colab-webapp/src/main/node/app/src/components/common/element/Avatar.tsx b/colab-webapp/src/main/node/app/src/components/common/element/Avatar.tsx index 4c4e7486df6..f96094b8a29 100644 --- a/colab-webapp/src/main/node/app/src/components/common/element/Avatar.tsx +++ b/colab-webapp/src/main/node/app/src/components/common/element/Avatar.tsx @@ -7,7 +7,7 @@ import { css, cx } from '@emotion/css'; import { User } from 'colab-rest-client'; -import React from 'react'; +import * as React from 'react'; import { br_full, space_lg, space_xs } from '../../../styling/style'; import Flex from '../layout/Flex'; diff --git a/colab-webapp/src/main/node/app/src/components/common/element/Badge.tsx b/colab-webapp/src/main/node/app/src/components/common/element/Badge.tsx index a24fc07e475..485e3521555 100644 --- a/colab-webapp/src/main/node/app/src/components/common/element/Badge.tsx +++ b/colab-webapp/src/main/node/app/src/components/common/element/Badge.tsx @@ -6,7 +6,7 @@ */ import { css, cx } from '@emotion/css'; -import React from 'react'; +import * as React from 'react'; import { br_md, ellipsisStyle, diff --git a/colab-webapp/src/main/node/app/src/components/common/element/CleverTextarea.tsx b/colab-webapp/src/main/node/app/src/components/common/element/CleverTextarea.tsx deleted file mode 100644 index db18c549995..00000000000 --- a/colab-webapp/src/main/node/app/src/components/common/element/CleverTextarea.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * The coLAB project - * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO - * - * Licensed under the MIT License - */ - -import * as React from 'react'; -import * as LiveHelper from '../../../LiveHelper'; -import logger from '../../../logger'; - -export interface CleverTextareaProps { - value: string; - onChange: (newValue: string) => void; - className?: string; -} - -/** - * Get new selection range by applying offsets to current selection - */ -function computeSelectionOffsets(offsets: LiveHelper.Offsets, node: HTMLTextAreaElement) { - const startIndex = node.selectionStart; - const endIndex = node.selectionEnd; - - const newRange = { - start: startIndex, - end: endIndex, - }; - logger.trace('Move selection ', startIndex, ':', endIndex, ' according to offsets: ', offsets); - - for (const sKey in offsets) { - const key = +sKey; - const offset = offsets[key]!; - if (key < startIndex) { - newRange.start += offset; - } - if (key < endIndex) { - newRange.end += offset; - } - } - - logger.trace('New Range: ', newRange); - return newRange; -} - -/** - * Managed textarea which try to keep selected text across updates - */ -export default function CleverTextarea({ - value, - onChange, - className, -}: CleverTextareaProps): JSX.Element { - // use a ref to manage the input directly - const inputRef = React.useRef(null); - - React.useLayoutEffect(() => { - const ta = inputRef.current; - if (ta != null) { - if (ta.value !== value) { - const diff = LiveHelper.getMicroChange(ta.value, value); - const offsets = LiveHelper.computeOffsets(diff); - const range = computeSelectionOffsets(offsets, ta); - ta.value = value; - ta.selectionStart = range.start; - ta.selectionEnd = range.end; - } - } - }, [value]); - - const onInternalChangeCb = React.useCallback( - (e: React.ChangeEvent) => { - const newValue = e.target.value; - onChange(newValue); - }, - [onChange], - ); - - return ( -