├── .dockerignore ├── .gitattributes ├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .gitlab-ci.yml ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ └── maven-wrapper.properties ├── .slugignore ├── .tern-project ├── .travis.yml ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── doc ├── CMD_Documentation.pdf └── CMD_Presentation.pdf ├── docker-compose.yml ├── docker ├── mysql.dockerfile └── scripts │ ├── jboss-configure-mysql-datasource.sh │ └── jboss-configure-postgresql-datasource.sh ├── frontend ├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .jshintrc ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── postscript.js ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ ├── background.jpeg │ │ ├── cmd_logo.png │ │ ├── logo.png │ │ ├── social.jpg │ │ ├── welcome_logo.png │ │ └── work.jpeg │ ├── components │ │ ├── Document.vue │ │ ├── Drawer.vue │ │ ├── Editor.vue │ │ ├── ErrorPage.vue │ │ ├── ForbiddenPage.vue │ │ ├── Login.vue │ │ ├── Preview.vue │ │ └── Welcome.vue │ ├── main.js │ ├── plugins │ │ ├── axios.js │ │ ├── chat.js │ │ ├── snotify.js │ │ └── vuetify.js │ ├── router │ │ └── index.js │ └── store │ │ ├── index.js │ │ └── modules │ │ ├── app.js │ │ └── login.js └── vue.config.js ├── heroku.yml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── resources ├── 3-tier-architecture.jpg ├── AppExampleDisplay.png └── client-server-architecture.jpg ├── sql ├── cmd.mwb └── cmd.sql ├── src ├── main │ ├── java │ │ └── org │ │ │ └── dhbw │ │ │ └── mosbach │ │ │ └── ai │ │ │ └── cmd │ │ │ ├── crdt │ │ │ ├── ActiveDocument.java │ │ │ ├── Message.java │ │ │ └── MessageBroker.java │ │ │ ├── db │ │ │ ├── CollaboratorDao.java │ │ │ ├── DocDao.java │ │ │ ├── HistoryDao.java │ │ │ ├── RepoDao.java │ │ │ └── UserDao.java │ │ │ ├── model │ │ │ ├── Collaborator.java │ │ │ ├── Doc.java │ │ │ ├── History.java │ │ │ ├── Repo.java │ │ │ └── User.java │ │ │ ├── security │ │ │ └── Hashing.java │ │ │ ├── services │ │ │ ├── AuthenticationService.java │ │ │ ├── CollaboratorService.java │ │ │ ├── DocumentService.java │ │ │ ├── RestEndpoint.java │ │ │ ├── RootService.java │ │ │ ├── ServiceEndpoints.java │ │ │ ├── mapper │ │ │ │ ├── JsonParseMapper.java │ │ │ │ ├── MapperJsonResponse.java │ │ │ │ ├── MethodNotAllowedMapper.java │ │ │ │ ├── NotAcceptableMapper.java │ │ │ │ ├── NotFoundMapper.java │ │ │ │ └── NotSupportedMapper.java │ │ │ ├── payload │ │ │ │ ├── CollaboratorInsertionModel.java │ │ │ │ ├── CollaboratorRemovalModel.java │ │ │ │ ├── DocumentAccessModel.java │ │ │ │ ├── DocumentInsertionModel.java │ │ │ │ ├── DocumentRemovalModel.java │ │ │ │ ├── DocumentTransferModel.java │ │ │ │ ├── LoginModel.java │ │ │ │ ├── Payload.java │ │ │ │ ├── PayloadParameters.java │ │ │ │ └── RegisterModel.java │ │ │ ├── response │ │ │ │ ├── BadRequest.java │ │ │ │ ├── DocumentListResponse.java │ │ │ │ ├── Forbidden.java │ │ │ │ ├── InternalServerError.java │ │ │ │ ├── LoginUserResponse.java │ │ │ │ ├── ResponseObject.java │ │ │ │ ├── ResponseParameters.java │ │ │ │ ├── Status.java │ │ │ │ ├── Success.java │ │ │ │ ├── Unauthorized.java │ │ │ │ └── entity │ │ │ │ │ ├── DocumentIcon.java │ │ │ │ │ └── DocumentListEntity.java │ │ │ ├── serialize │ │ │ │ ├── LocalDateTimeDeserializer.java │ │ │ │ └── LocalDateTimeSerializer.java │ │ │ └── validation │ │ │ │ ├── ModelValidation.java │ │ │ │ ├── ValidationResult.java │ │ │ │ ├── authentication │ │ │ │ ├── LoginValidation.java │ │ │ │ └── RegisterValidation.java │ │ │ │ ├── basic │ │ │ │ ├── BasicCollaboratorValidation.java │ │ │ │ ├── BasicDocumentValidation.java │ │ │ │ ├── BasicFieldValidation.java │ │ │ │ └── BasicUserValidation.java │ │ │ │ ├── collaborator │ │ │ │ ├── CollaboratorInsertionValidation.java │ │ │ │ └── CollaboratorRemovalValidation.java │ │ │ │ └── document │ │ │ │ ├── DocumentAccessValidation.java │ │ │ │ ├── DocumentInsertionValidation.java │ │ │ │ ├── DocumentRemovalValidation.java │ │ │ │ └── DocumentTransferValidation.java │ │ │ ├── session │ │ │ └── SessionUtil.java │ │ │ ├── util │ │ │ ├── CmdConfig.java │ │ │ ├── HasAccess.java │ │ │ ├── MatchTools.java │ │ │ └── MessageType.java │ │ │ └── websocket │ │ │ ├── Endpoint.java │ │ │ ├── MessageDecoder.java │ │ │ └── MessageEncoder.java │ ├── resources │ │ └── META-INF │ │ │ ├── beans.xml │ │ │ ├── log4j.properties │ │ │ └── persistence.xml │ └── webapp │ │ └── .gitkeep └── test │ ├── java │ └── org │ │ └── dhbw │ │ └── mosbach │ │ └── ai │ │ └── cmd │ │ ├── crdt │ │ ├── ActiveDocumentTest.java │ │ └── MessageBrokerTest.java │ │ ├── db │ │ ├── DocDaoIT.java │ │ └── UserDaoIT.java │ │ ├── extension │ │ └── listener │ │ │ ├── FailsafeTestPrintListener.java │ │ │ ├── SurefireTestPrintListener.java │ │ │ ├── log │ │ │ ├── LogPrefix.java │ │ │ └── TestLogger.java │ │ │ ├── result │ │ │ ├── ResultAccumulator.java │ │ │ ├── RunTime.java │ │ │ └── TestSuiteResult.java │ │ │ └── util │ │ │ ├── DescriptionScanner.java │ │ │ └── TestCasePrinter.java │ │ ├── security │ │ └── HashingTest.java │ │ ├── services │ │ ├── AuthenticationServiceIT.java │ │ ├── CollaboratorServiceIT.java │ │ ├── DocumentServiceIT.java │ │ ├── helper │ │ │ ├── Authenticator.java │ │ │ ├── ClientJson.java │ │ │ └── JsonUtil.java │ │ └── payload │ │ │ └── TestLoginModel.java │ │ └── test │ │ ├── config │ │ ├── DeploymentConfig.java │ │ ├── TestConfig.java │ │ ├── TestUser.java │ │ └── TestUsers.java │ │ ├── helper │ │ ├── DeploymentPackager.java │ │ └── PasswordGenerator.java │ │ ├── include │ │ ├── DependencyIncludes.java │ │ └── PackageIncludes.java │ │ └── resources │ │ ├── Datasets.java │ │ └── Scripts.java │ └── resources │ ├── META-INF │ └── test-persistence.xml │ ├── WEB-INF │ └── web.xml │ ├── arquillian-cube.xml │ ├── arquillian-standalone-h2db.xml │ ├── arquillian.xml │ ├── datasets │ ├── documents.yml │ ├── repos.yml │ └── users.yml │ ├── docker │ ├── Dockerfile │ └── docker-compose.yml │ └── scripts │ ├── disableReferentialIntegrity.sql │ └── resetTableIdentities.sql └── system.properties /.dockerignore: -------------------------------------------------------------------------------- 1 | ### Dockerignore List 2 | 3 | # Git Files 4 | .git/ 5 | .gitignore 6 | .gitattributes 7 | 8 | # Travis File 9 | .travis.yml 10 | 11 | # README file 12 | README.md 13 | 14 | # Maven Wrapper JAR 15 | .mvn/wrapper/maven-wrapper.jar 16 | 17 | # Compiled class file 18 | *.class 19 | 20 | # Log file 21 | *.log 22 | 23 | # BlueJ files 24 | *.ctxt 25 | 26 | # Mobile Tools for Java (J2ME) 27 | .mtj.tmp/ 28 | 29 | # Package Files # 30 | *.jar 31 | *.nar 32 | *.ear 33 | *.zip 34 | *.tar.gz 35 | *.rar 36 | 37 | # Other Binaries 38 | *.7z 39 | *.dmg 40 | *.gz 41 | *.iso 42 | *.tar 43 | *.sar 44 | 45 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 46 | hs_err_pid* 47 | 48 | # Eclipse 49 | .classpath 50 | .project 51 | .settings/ 52 | 53 | # IntelliJ project files 54 | *.iml 55 | *.iws 56 | *.ipr 57 | .idea/ 58 | 59 | # Mac OS X 60 | .DS_Store 61 | 62 | # Windows thumbnail cache files 63 | Thumbs.db 64 | ehthumbs.db 65 | ehthumbs_vista.db 66 | 67 | # Dump file 68 | *.stackdump 69 | 70 | # Folder config file 71 | [Dd]esktop.ini 72 | 73 | # Maven 74 | log/ 75 | pom.xml.tag 76 | pom.xml.releaseBackup 77 | pom.xml.versionsBackup 78 | pom.xml.next 79 | release.properties 80 | dependency-reduced-pom.xml 81 | buildNumber.properties 82 | .mvn/timing.properties 83 | target/ 84 | 85 | # Vim Temporary Files 86 | *.swp 87 | 88 | # Node modules 89 | frontend/node_modules/ 90 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.bat text eol=crlf 3 | *.java text eol=lf 4 | *.sh text eol=lf 5 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Collaborative Markdown Editor 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up JDK 11 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 11 16 | - name: Build with Maven 17 | run: mvn -B integration-test 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # Other Binaries 23 | *.7z 24 | *.dmg 25 | *.gz 26 | *.iso 27 | *.tar 28 | *.sar 29 | 30 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 31 | hs_err_pid* 32 | 33 | # Eclipse 34 | .classpath 35 | .project 36 | .settings/ 37 | 38 | # IntelliJ project files 39 | *.iml 40 | *.iws 41 | *.ipr 42 | .idea/ 43 | 44 | # Mac OS X 45 | .DS_Store 46 | 47 | # Windows thumbnail cache files 48 | Thumbs.db 49 | ehthumbs.db 50 | ehthumbs_vista.db 51 | 52 | # Dump file 53 | *.stackdump 54 | 55 | # Folder config file 56 | [Dd]esktop.ini 57 | 58 | # Maven 59 | target/ 60 | pom.xml.tag 61 | pom.xml.releaseBackup 62 | pom.xml.versionsBackup 63 | pom.xml.next 64 | release.properties 65 | dependency-reduced-pom.xml 66 | buildNumber.properties 67 | .mvn/timing.properties 68 | .mvn/wrapper/maven-wrapper.jar 69 | 70 | # Vim Temporary Files 71 | *.swp 72 | 73 | # Built frontend 74 | src/main/webapp/css 75 | src/main/webapp/img 76 | src/main/webapp/js 77 | src/main/webapp/favicon.ico 78 | src/main/webapp/index.html 79 | 80 | # Node modules 81 | frontend/node_modules/ 82 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - verify 3 | - build 4 | - test 5 | 6 | cache: 7 | paths: 8 | - frontend/node_modules 9 | 10 | lint_frontend: 11 | image: "node:10.6" 12 | stage: verify 13 | script: 14 | - cd frontend/ 15 | - npm install 16 | - npm run lint 17 | 18 | build_app: 19 | image: "maven:3.6.1-slim" 20 | stage: build 21 | script: mvn install -DskipTests=true -Dmaven.javadoc.skip=true -V 22 | 23 | run_frontend_tests: 24 | image: node:10.16 25 | stage: test 26 | script: 27 | - cd frontend 28 | - npm run test 29 | 30 | run_backend_tests: 31 | image: "maven:3.6.1-slim" 32 | stage: test 33 | script: 34 | - mvn verify -P skip-frontend-build,arquillian-wildfly-managed 35 | artifacts: 36 | paths: 37 | - target/CMD.war 38 | 39 | run_docker_build: 40 | image: "tiangolo/docker-with-compose:latest" 41 | stage: test 42 | script: 43 | - docker-compose build 44 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.io.File; 18 | import java.io.FileInputStream; 19 | import java.io.FileOutputStream; 20 | import java.io.IOException; 21 | import java.net.Authenticator; 22 | import java.net.PasswordAuthentication; 23 | import java.net.URL; 24 | import java.nio.channels.Channels; 25 | import java.nio.channels.ReadableByteChannel; 26 | import java.util.Properties; 27 | 28 | public class MavenWrapperDownloader { 29 | 30 | private static final String WRAPPER_VERSION = "0.5.4"; 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 35 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + " .jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String[] args) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if (mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if (mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if (!outputFile.getParentFile().exists()) { 87 | if (!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 106 | String username = System.getenv("MVNW_USERNAME"); 107 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 108 | Authenticator.setDefault(new Authenticator() { 109 | @Override 110 | protected PasswordAuthentication getPasswordAuthentication() { 111 | return new PasswordAuthentication(username, password); 112 | } 113 | }); 114 | } 115 | URL website = new URL(urlString); 116 | ReadableByteChannel rbc; 117 | rbc = Channels.newChannel(website.openStream()); 118 | FileOutputStream fos = new FileOutputStream(destination); 119 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 120 | fos.close(); 121 | rbc.close(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.4/maven-wrapper-0.5.4.jar 3 | -------------------------------------------------------------------------------- /.slugignore: -------------------------------------------------------------------------------- 1 | # Ignore these files and directories for the slug on Heroku 2 | # not needed to run the application 3 | 4 | # Compiled class file 5 | *.class 6 | 7 | # Log files 8 | *.log 9 | 10 | # BlueJ files 11 | *.ctxt 12 | 13 | # Mobile Tools for Java (J2ME) 14 | .mtj.tmp/ 15 | 16 | # Package Files # 17 | *.jar 18 | *.war 19 | *.nar 20 | *.ear 21 | *.zip 22 | *.tar.gz 23 | *.rar 24 | 25 | # Other Binaries 26 | *.7z 27 | *.dmg 28 | *.gz 29 | *.iso 30 | *.tar 31 | *.sar 32 | 33 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 34 | hs_err_pid* 35 | 36 | # Node Modules 37 | frontend/node_modules/ 38 | 39 | # PDFs 40 | *.pdf 41 | 42 | # Resources 43 | /resources/ 44 | 45 | # Project files 46 | .tern-project 47 | 48 | # SQL Database Schema creation scripts 49 | /sql/ 50 | 51 | # Git Files 52 | .gitattributes 53 | .gitignore 54 | 55 | # Travis 56 | .travis.yml 57 | 58 | # Docker 59 | .dockerignore 60 | Dockerfile.mysql 61 | Dockerfile.wildfly 62 | docker-compose.yml 63 | 64 | # Maven 65 | target/ 66 | pom.xml.tag 67 | pom.xml.releaseBackup 68 | pom.xml.versionsBackup 69 | pom.xml.next 70 | release.properties 71 | dependency-reduced-pom.xml 72 | buildNumber.properties 73 | .mvn/timing.properties 74 | .mvn/wrapper/maven-wrapper.jar 75 | 76 | # Eclipse 77 | .classpath 78 | .project 79 | .settings/ 80 | 81 | # IntelliJ project files 82 | *.iml 83 | *.iws 84 | *.ipr 85 | .idea/ 86 | 87 | # Mac OS X 88 | .DS_Store 89 | 90 | # Windows thumbnail cache files 91 | Thumbs.db 92 | ehthumbs.db 93 | ehthumbs_vista.db 94 | 95 | # Dump file 96 | *.stackdump 97 | 98 | # Folder config file 99 | [Dd]esktop.ini 100 | -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "guess-types": { 4 | 5 | }, 6 | "outline": { 7 | 8 | }, 9 | "angular": { 10 | 11 | } 12 | }, 13 | "libs": [ 14 | "browser" 15 | ] 16 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: oraclejdk11 3 | 4 | # Cache Maven local repository and node 5 | # modules 6 | cache: 7 | directories: 8 | - ${HOME}/.m2 # Maven repository 9 | - frontend/node_modules # NPM modules 10 | 11 | install: 12 | - ./mvnw install -DskipTests=true -Dmaven.javadoc.skip=true -B -V 13 | 14 | script: 15 | - cd frontend/ && npm run test && cd .. 16 | - ./mvnw test -B 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Specify WildFly version and app home 2 | # directory as arguments 3 | ARG WILDFLY_VERSION=16.0.0.Final 4 | 5 | # 1. Stage: Build frontend and backend 6 | # Use Maven base image 7 | FROM maven:3.6.0-jdk-11-slim AS build 8 | 9 | ENV APP_HOME /app 10 | 11 | COPY frontend ${APP_HOME}/frontend 12 | COPY src ${APP_HOME}/src 13 | COPY pom.xml ${APP_HOME}/pom.xml 14 | 15 | # Set the working directory to the app home directory 16 | WORKDIR ${APP_HOME} 17 | 18 | # Install Git as this is required for a custom 19 | # npm dependency 20 | RUN apt-get update && apt-get --assume-yes install git 21 | 22 | # Build the frontend and backend of the application 23 | RUN mvn install -P deployment -DskipTests=true -Dmaven.javadoc.skip=true -B -V 24 | 25 | # 2. Stage: Configure WildFly and deploy application 26 | # Use the WildFly base image to deploy the built application 27 | FROM jboss/wildfly:${WILDFLY_VERSION} AS server 28 | 29 | ARG buildno 30 | 31 | # Set the working directory to the WildFly home directory 32 | WORKDIR ${JBOSS_HOME} 33 | 34 | # Ports used by the WildFly instance 35 | ENV WEBAPP_PORT 8080/tcp 36 | ENV ADMINISTRATION_PORT 9990/tcp 37 | 38 | # WildFly settings 39 | ENV WILDFLY_USER wildfly 40 | ENV WILDFLY_PASSWORD wildfly 41 | 42 | # MySQL version 43 | ENV MYSQL_VERSION 8.0.15 44 | 45 | # Environment variables for the WildFly datasource 46 | ENV DB_NAME cmd 47 | ENV DB_USER mysqluser 48 | ENV DB_PASSWORD mysqlpass 49 | ENV DB_PORT 3306 50 | ENV DB_URI mysql-instance:${DB_PORT} 51 | 52 | # Path variables 53 | ENV JBOSS_CLI ${JBOSS_HOME}/bin/jboss-cli.sh 54 | ENV DEPLOYMENT_DIR ${JBOSS_HOME}/standalone/deployments 55 | ENV APP_DIR ./collaborative-markdown-editor 56 | 57 | RUN echo "Build Number: ${buildno}" 58 | 59 | # Add the configuration script to add MySQL driver 60 | # and datasource to the WildFly instance 61 | COPY docker/scripts/jboss-configure-mysql-datasource.sh . 62 | 63 | # Switch temporarily to root to get the permission to 64 | # make the configuration script executable 65 | USER root 66 | RUN chmod +x ./jboss-configure-mysql-datasource.sh 67 | USER jboss 68 | 69 | # Execute the configuration script 70 | RUN ./jboss-configure-mysql-datasource.sh 71 | 72 | # Remove the script 73 | RUN rm -f ./jboss-configure-datasource.sh 74 | 75 | # Deploy the application as ROOT app to WildFly 76 | COPY --from=build /app/target/ROOT.war ${DEPLOYMENT_DIR}/ 77 | 78 | # Expose the webapp port 79 | EXPOSE ${WEBAPP_PORT} 80 | 81 | # Start the WildFly 82 | CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "-bmanagement", "0.0.0.0"] 83 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: $JBOSS_HOME/bin/standalone.sh -b 0.0.0.0 -Djboss.http.port=$PORT 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Collaborative Markdown Editor [](https://travis-ci.com/mortenterhart/collaborative-markdown-editor) 2 | 3 | [](https://heroku.com/deploy) 4 | [](https://deploy.zeet.co/?url=https://github.com/mortenterhart/collaborative-markdown-editor) 5 | 6 | A Markdown Editor with collaboration capabilities. Start with new documents, add your friends and 7 | colleagues to your document and share amazing ideas. 8 | 9 |  10 | 11 | ## Features 12 | 13 | * User Management 14 | * Users can create and delete documents in their work space 15 | * Synchronization of the clients to be able to collaborate 16 | * Markdown syntax support in the editor 17 | * WYSIWYG toolbar for Markdown 18 | * Live Preview of editor content rendered in Markdown 19 | * Add and remove collaborators to a project 20 | * Preview who is working on a document 21 | * Transfer the ownership of a document 22 | * Chat to communicate with other users currently working on the same document 23 | * Chat commands 24 | 25 | ## Project Idea 26 | 27 | Our idea was born in the 4th semester. To prepare for the exams, Morten in particular summarized 28 | some lecture scripts and used the online markdown editor StackEdit for this purpose. Markdown is 29 | a markup language for structuring and formatting text documents with little syntax. The big problem 30 | with StackEdit, however, was that it is not multi-user. 31 | 32 | ## Setup 33 | 34 | ### Docker-Compose 35 | 36 | First you should check which versions of Docker and Docker-Compose you have. It should be at least 37 | Docker version 18.0 and Docker-Compose 1.23, so that the declared features are fully supported. To 38 | build and start the containers you can use the simple command 39 | 40 | ```bash 41 | docker-compose up 42 | ``` 43 | 44 | right from the project directory. A whole series of operations are now performed here, which are 45 | listed below. 46 | 47 | 1. The basic images `mysql` and `jboss/wildfly` are downloaded from Docker Hub. 48 | 2. The MySQL image is built, a root user and a database user are set up, and the database is 49 | initialized. 50 | 3. The WildFly image is built. 51 | 4. First, Maven is used to build the application in another container. This includes building 52 | the frontend directly, since the Maven frontend plugin is used. 53 | 5. An administrator account is added to the WildFly. 54 | 6. The MySQL connector is downloaded and installed at WildFly. In addition, a data source is 55 | set up directly to allow the connection to the database in the other container. 56 | 7. The WAR archive previously built by Maven is deployed to WildFly. 57 | 8. Wildfly and MySQL are started simultaneously. 58 | 59 | Open your browser at http://localhost:8080 and see the editor appearing. 60 | 61 | ### Maven Deploy 62 | 63 | Maven Deploy can be used as an alternative to the Docker image. This is made possible by the 64 | Wildfly Maven plugin. Again, the plugin downloads and starts a Wildfly 16.0.0.Final and 65 | configures it with MySQL Connector and Datasource. 66 | 67 | To do this, the Wildfly must first be started with the command 68 | 69 | ```bash 70 | mvn wildfly:start 71 | ``` 72 | 73 | This is required to then add the MySQL connector and data source and deploy the application. 74 | Before you can continue, however, you must make sure that a MySQL database is present on the 75 | machine and running. The connection information (JDBC URL, username, password) must be stored 76 | in Maven `pom.xml` in the properties 77 | 78 | * `wildfly.datasource.jdbc.url` 79 | * `wildfly.datasource.username` 80 | * `wildfly.datasource.password` 81 | 82 | to perform the complete Maven lifecycle from compiling the software, building the Vue.js frontend, 83 | generating a JaCoCo coverage report, packing the application into a WAR archive, configuring the 84 | Wildfly and deploying the application. 85 | 86 | ```bash 87 | mvn wildfly:deploy -P deployment -DskipTests=true 88 | ``` 89 | 90 | Your Wildfly should now be deploying your application and you can verify by visiting http://localhost:8080. 91 | 92 | ## Architecture 93 | 94 | ### Client-Server-Model 95 | 96 |  97 | 98 | ### 3-Tier Architecture 99 | 100 |  101 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Collaborative Markdown Editor", 3 | "description": "A collaborative Markdown Editor to write documents with other people and share ideas", 4 | "website": "https://collaborative-markdown-editor.herokuapp.com", 5 | "repository": "https://github.com/mortenterhart/collaborative-markdown-editor", 6 | "logo": "https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/master/frontend/src/assets/cmd_logo.png", 7 | 8 | "scripts": { 9 | "postdeploy": "./mvnw clean" 10 | }, 11 | 12 | "env": { 13 | "MAVEN_CUSTOM_OPTS": { 14 | "value": "-P deployment -DskipTests -Dit.skipTests", 15 | "description": "Maven options to build in deployment mode and to skip tests", 16 | "required": true 17 | } 18 | }, 19 | 20 | "addons": [ 21 | "heroku-postgresql" 22 | ], 23 | 24 | "buildpacks": [ 25 | { 26 | "url": "heroku/java" 27 | }, 28 | { 29 | "url": "https://github.com/mortenterhart/heroku-buildpack-wildfly" 30 | }, 31 | { 32 | "url": "https://github.com/mortenterhart/heroku-buildpack-wildfly-postgresql" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /doc/CMD_Documentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/doc/CMD_Documentation.pdf -------------------------------------------------------------------------------- /doc/CMD_Presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/doc/CMD_Presentation.pdf -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker-Compose file for the Collaborative Markdown Editor 2 | # 3 | # This compose file defines the 'webapp' and 'mysql' services 4 | # which are automatically built using the Dockerfiles and 5 | # configured using external scripts from the repository. 6 | 7 | version: "3.6" 8 | 9 | services: 10 | webapp: 11 | image: "cmd-wildfly-webapp:latest" 12 | hostname: cmd-wildfly-instance 13 | container_name: cmd-wildfly-webapp 14 | build: 15 | context: . 16 | dockerfile: Dockerfile 17 | args: 18 | buildno: 1 19 | ports: 20 | - "8080:8080" 21 | links: 22 | - "mysql:mysql-instance" 23 | networks: 24 | - cmd_network 25 | restart: on-failure 26 | depends_on: 27 | - mysql 28 | 29 | mysql: 30 | image: "mysql:8.0.15" 31 | hostname: mysql-instance 32 | container_name: cmd-mysql-instance 33 | environment: 34 | MYSQL_ROOT_PASSWORD: mysqlroot 35 | MYSQL_USER: mysqluser 36 | MYSQL_PASSWORD: mysqlpass 37 | MYSQL_DATABASE: cmd 38 | MYSQL_PORT: 3306 39 | deploy: 40 | mode: replicated 41 | replicas: 5 42 | restart_policy: 43 | condition: on-failure 44 | volumes: 45 | - type: volume 46 | source: mysql_data 47 | target: /var/lib/mysql 48 | volume: 49 | nocopy: true 50 | networks: 51 | - cmd_network 52 | ports: 53 | - "3306:3306" 54 | restart: on-failure 55 | 56 | volumes: 57 | mysql_data: 58 | name: cmd-mysql-data 59 | labels: 60 | org.dhbw.mosbach.cmd.license: LGPL 3.0 61 | 62 | networks: 63 | cmd_network: 64 | name: cmd_network 65 | -------------------------------------------------------------------------------- /docker/mysql.dockerfile: -------------------------------------------------------------------------------- 1 | # Specify the MySQL version to be downloaded 2 | ARG MYSQL_VERSION=8.0.15 3 | 4 | # Pull the official MySQL image 5 | FROM mysql:${MYSQL_VERSION} AS database 6 | 7 | ARG buildno 8 | 9 | # Configure MySQL root user 10 | ENV MYSQL_ROOT_PASSWORD mysqlroot 11 | 12 | # Configure MySQL database user 13 | ENV MYSQL_USER mysqluser 14 | ENV MYSQL_PASSWORD mysqlpass 15 | 16 | # Configure MySQL database 17 | ENV MYSQL_DATABASE cmd 18 | ENV MYSQL_PORT 3306 19 | 20 | RUN echo "Build Number: ${buildno}" 21 | 22 | # Expose the standard MySQL port 23 | EXPOSE ${MYSQL_PORT} 24 | -------------------------------------------------------------------------------- /docker/scripts/jboss-configure-mysql-datasource.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RED=$'\e[31m' 4 | GREEN=$'\e[32m' 5 | RESET=$'\e[0m' 6 | 7 | function info() { 8 | echo -e "${GREEN}==> $*${RESET}" 9 | } 10 | 11 | function error() { 12 | echo -e "${RED}==> ERROR: $*${RESET}" 13 | } 14 | 15 | function safe_run() { 16 | "$@" 17 | local exit_code=$? 18 | if [ "${exit_code}" -ne 0 ]; then 19 | error "The command '$*' exited with non-zero exit code ${exit_code}." 20 | exit "${exit_code}" 21 | fi 22 | } 23 | 24 | function check_server_state() { 25 | ${JBOSS_CLI} -c ":read-attribute(name=server-state)" 26 | } 27 | 28 | info "Setting up MySQL ${MYSQL_VERSION} Datasource for WildFly ${WILDFLY_VERSION}" 29 | 30 | info "Adding WildFly administration user" 31 | safe_run ${JBOSS_HOME}/bin/add-user.sh --user "${WILDFLY_USER}" --password "${WILDFLY_PASSWORD}" --silent 32 | 33 | info "Starting WildFly server" 34 | ${JBOSS_HOME}/bin/standalone.sh & 35 | 36 | info "Waiting for the server to boot" 37 | until check_server_state 2> /dev/null | grep -q running; do 38 | sleep 1; 39 | done 40 | info "WildFly has started up" 41 | 42 | info "Downloading MySQL driver" 43 | MYSQL_CONNECTOR="mysql-connector-java-${MYSQL_VERSION}.jar" 44 | MYSQL_CONNECTOR_LOCATION="/tmp/${MYSQL_CONNECTOR}" 45 | MYSQL_CONNECTOR_URL="https://repo.maven.apache.org/maven2/mysql/mysql-connector-java/${MYSQL_VERSION}/${MYSQL_CONNECTOR}" 46 | safe_run curl --location \ 47 | --output "${MYSQL_CONNECTOR_LOCATION}" \ 48 | --url "${MYSQL_CONNECTOR_URL}" 49 | 50 | info "Download completed, verifying SHA1 checksum for ${MYSQL_CONNECTOR}" 51 | MYSQL_CONNECTOR_SHA1="${MYSQL_CONNECTOR}.sha1" 52 | MYSQL_CONNECTOR_SHA1_LOCATION="/tmp/${MYSQL_CONNECTOR_SHA1}" 53 | safe_run curl --location \ 54 | --output "${MYSQL_CONNECTOR_SHA1_LOCATION}" \ 55 | --url "https://repo.maven.apache.org/maven2/mysql/mysql-connector-java/${MYSQL_VERSION}/${MYSQL_CONNECTOR_SHA1}" 56 | 57 | if ! printf "%s %s\n" "$(cat "${MYSQL_CONNECTOR_SHA1_LOCATION}")" "${MYSQL_CONNECTOR_LOCATION}" | sha1sum --check; then 58 | error "Invalid SHA1 checksum for ${MYSQL_CONNECTOR} downloaded from ${MYSQL_CONNECTOR_URL}" 59 | exit 1 60 | fi 61 | info "Verification successful" 62 | 63 | info "Adding MySQL module" 64 | MODULE_NAME="com.mysql" 65 | safe_run ${JBOSS_CLI} --connect \ 66 | --command="module add --name=${MODULE_NAME} --resources=${MYSQL_CONNECTOR_LOCATION} --dependencies=javax.api,javax.transaction.api" 67 | 68 | info "Installing MySQL driver" 69 | safe_run ${JBOSS_CLI} --connect \ 70 | --command="/subsystem=datasources/jdbc-driver=mysql:add(driver-name=mysql,driver-module-name=${MODULE_NAME},driver-class-name=com.mysql.cj.jdbc.Driver,driver-xa-datasource-class-name=com.mysql.cj.jdbc.MysqlXADataSource)" 71 | 72 | info "Creating a new datasource" 73 | safe_run ${JBOSS_CLI} --connect --command="data-source add 74 | --name=${DB_NAME}DS 75 | --jndi-name=java:jboss/datasources/cmdDS 76 | --user-name=${DB_USER} 77 | --password=${DB_PASSWORD} 78 | --driver-name=mysql 79 | --connection-url=jdbc:mysql://${DB_URI}/${DB_NAME} 80 | --max-pool-size=25 81 | --blocking-timeout-wait-millis=5000 82 | --enabled=true 83 | --jta=true 84 | --use-ccm=false 85 | --valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker 86 | --background-validation=true 87 | --exception-sorter-class-name=org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter" 88 | 89 | info "Shutting down WildFly and Cleaning up" 90 | safe_run ${JBOSS_CLI} --connect --command=":shutdown" 91 | 92 | rm -rf ${JBOSS_HOME}/standalone/configuration/standalone_xml_history/ ${JBOSS_HOME}/standalone/log/* 93 | rm -f /tmp/*.jar /tmp/*.sha1 94 | -------------------------------------------------------------------------------- /docker/scripts/jboss-configure-postgresql-datasource.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RED=$'\e[31m' 4 | GREEN=$'\e[32m' 5 | RESET=$'\e[0m' 6 | 7 | function info() { 8 | echo -e "${GREEN}==> $*${RESET}" 9 | } 10 | 11 | function error() { 12 | echo -e "${RED}==> ERROR: $*${RESET}" 13 | } 14 | 15 | function safe_run() { 16 | "$@" 17 | local exit_code=$? 18 | if [ "${exit_code}" -ne 0 ]; then 19 | error "The command '$*' exited with non-zero exit code ${exit_code}." 20 | exit "${exit_code}" 21 | fi 22 | } 23 | 24 | function check_server_state() { 25 | ${JBOSS_CLI} -c ":read-attribute(name=server-state)" 26 | } 27 | 28 | info "Setting up PostgreSQL Datasource for WilFly ${WILDFLY_VERSION}\n" 29 | 30 | info "Adding WildFly administration user" 31 | safe_run ${JBOSS_HOME}/bin/add-user.sh --user "${WILDFLY_USER}" --password "${WILDFLY_PASSWORD}" --silent 32 | 33 | info "Starting WildFly server" 34 | ${JBOSS_HOME}/bin/standalone.sh & 35 | 36 | info "Waiting for the server to boot" 37 | until check_server_state 2> /dev/null | grep -q running; do 38 | sleep 1; 39 | done 40 | info "WildFly has started up" 41 | 42 | info "Downloading PostgreSQL driver" 43 | POSTGRESQL_CONNECTOR="/tmp/postgresql-${POSTGRESQL_VERSION}.jar" 44 | MAVEN_DOWNLOAD_URL="http://central.maven.org/maven2/org/postgresql/postgresql/${POSTGRESQL_VERSION}/postgresql-${POSTGRESQL_VERSION}.jar" 45 | safe_run curl --location \ 46 | --output "${POSTGRESQL_CONNECTOR}" \ 47 | --url "${MAVEN_DOWNLOAD_URL}" 48 | 49 | info "PostgreSQL Driver downloaded, verifying SHA1 checksum" 50 | if ! sha1sum "${POSTGRESQL_CONNECTOR}" | grep "${POSTGRESQL_SHA1}" > /dev/null; then 51 | error "Invalid SHA1 for ${POSTGRESQL_CONNECTOR} downloaded from ${MAVEN_DOWNLOAD_URL}" 52 | exit 1 53 | fi 54 | info "Verified successfully" 55 | 56 | info "Adding PostgreSQL module" 57 | MODULE_NAME="org.postgresql" 58 | safe_run ${JBOSS_CLI} --connect \ 59 | --command="module add --name=${MODULE_NAME} --resources=${POSTGRESQL_CONNECTOR} --dependencies=javax.api,javax.transaction.api" 60 | 61 | info "Installing PostgreSQL driver" 62 | safe_run ${JBOSS_CLI} --connect \ 63 | --command="/subsystem=datasources/jdbc-driver=postgresql:add(driver-name=postgresql,driver-module-name=${MODULE_NAME},driver-xa-datasource-class-name=org.postgresql.xa.PGXADataSource)" 64 | 65 | info "Creating a new datasource" 66 | safe_run ${JBOSS_CLI} --connect --command="data-source add 67 | --name=${DB_NAME}DS 68 | --jndi-name=java:jboss/datasources/cmdDS 69 | --driver-name=postgresql 70 | --connection-url=${DATABASE_URL} 71 | --max-pool-size=25 72 | --blocking-timeout-wait-millis=5000 73 | --enabled=true 74 | --jta=true 75 | --use-ccm=false 76 | --valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker 77 | --background-validation=true 78 | --exception-sorter-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter" 79 | 80 | info "Shutting down WildFly and Cleaning up" 81 | safe_run ${JBOSS_CLI} --connect --command=":shutdown" 82 | 83 | rm -rf ${JBOSS_HOME}/standalone/configuration/standalone_xml_history/ ${JBOSS_HOME}/standalone/log/* 84 | rm -f /tmp/*.jar 85 | 86 | info "PostgreSQL Datasource configuration done" 87 | -------------------------------------------------------------------------------- /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /frontend/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 6 3 | } 4 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # cme 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "collaborative-markdown-editor-frontend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "postbuild": "node postscript.js" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.21.1", 13 | "core-js": "^2.6.9", 14 | "debounce": "^1.2.0", 15 | "js-cookie": "^2.2.1", 16 | "marked": "^0.7.0", 17 | "vue": "^2.6.6", 18 | "vue-axios": "^2.1.4", 19 | "vue-beautiful-chat": "git+https://github.com/MSkrzypietz/vue-beautiful-chat.git", 20 | "vue-router": "^3.1.3", 21 | "vue-simplemde": "^0.5.2", 22 | "vue-snotify": "^3.2.1", 23 | "vuetify": "^1.5.18", 24 | "vuex": "^3.1.1", 25 | "vuex-persistedstate": "^2.5.4" 26 | }, 27 | "devDependencies": { 28 | "@vue/cli-plugin-babel": "^3.11.0", 29 | "@vue/cli-plugin-eslint": "^4.3.0", 30 | "@vue/cli-service": "^4.2.3", 31 | "babel-eslint": "^10.0.3", 32 | "eslint": "^5.8.0", 33 | "eslint-plugin-vue": "^5.2.3", 34 | "node-sass": "^4.13.1", 35 | "sass-loader": "^8.0.2", 36 | "stylus": "^0.54.7", 37 | "stylus-loader": "^3.0.1", 38 | "vue-cli-plugin-vuetify": "^0.5.0", 39 | "vue-template-compiler": "^2.5.21", 40 | "vuetify-loader": "^1.4.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/postscript.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec; 2 | 3 | function log(error, stdout, stderr) { 4 | console.log(stdout); 5 | console.log(stderr); 6 | } 7 | 8 | const os = require('os'); 9 | 10 | const webappPath = "../src/main/webapp"; 11 | 12 | if (os.type() === 'Darwin' || os.type() === 'Linux') { 13 | exec(`rm -r "${webappPath}"/*`, log); 14 | exec(`mv dist/* "${webappPath}"`, log); 15 | } else if (os.type() === 'Windows_NT') { 16 | exec(`del /S /Q "${webappPath}"\\*`, log); 17 | exec(`xcopy dist\\* "${webappPath}"\\* /s /e /i /Y`, log); 18 | } else { 19 | throw new Error("Unsupported OS found: " + os.type()); 20 | } 21 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenterhart/collaborative-markdown-editor/50ba075d605336ed727324dda2aa960255af01c2/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 |The resource {{ resourcePath }} you requested was not found on this server!
6 | Click here to return to the homepage.
You are not permitted to access this document!
6 | Click here to return to the homepage.
24 | * Pre-condition for this method is that the parser points to the 25 | * first event that is part of value to deserializer (and which 26 | * is never JSON 'null' literal, more on this below): for simple 27 | * types it may be the only value; and for structured types the 28 | * Object start marker or a FIELD_NAME. 29 | *
30 | *31 | * The two possible input conditions for structured types result 32 | * from polymorphism via fields. In the ordinary case, Jackson 33 | * calls this method when it has encountered an OBJECT_START, 34 | * and the method implementation must advance to the next token to 35 | * see the first field name. If the application configures 36 | * polymorphism via a field, then the object looks like the following. 37 | *
38 | * { 39 | * "@class": "class name", 40 | * ... 41 | * } 42 | *43 | * Jackson consumes the two tokens (the @class field name 44 | * and its value) in order to learn the class and select the deserializer. 45 | * Thus, the stream is pointing to the FIELD_NAME for the first field 46 | * after the @class. Thus, if you want your method to work correctly 47 | * both with and without polymorphism, you must begin your method with: 48 | *
49 | * if (p.getCurrentToken() == JsonToken.START_OBJECT) { 50 | * p.nextToken(); 51 | * } 52 | *53 | * This results in the stream pointing to the field name, so that 54 | * the two conditions align. 55 | *
56 | * Post-condition is that the parser will point to the last 57 | * event that is part of deserialized value (or in case deserialization 58 | * fails, event that was not recognized or usable, which may be 59 | * the same event as the one it pointed to upon call). 60 | *
61 | * Note that this method is never called for JSON null literal,
62 | * and thus deserializers need (and should) not check for it.
63 | *
64 | * @param jsonParser Parsed used for reading JSON content
65 | * @param context Context that can be used to access information about
66 | * this deserialization activity.
67 | * @return Deserialized value
68 | */
69 | @Override
70 | public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException, JsonProcessingException {
71 | return LocalDateTime.parse(jsonParser.readValueAs(String.class));
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/org/dhbw/mosbach/ai/cmd/services/serialize/LocalDateTimeSerializer.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.services.serialize;
2 |
3 | import com.fasterxml.jackson.core.JsonGenerator;
4 | import com.fasterxml.jackson.databind.JsonSerializer;
5 | import com.fasterxml.jackson.databind.SerializerProvider;
6 | import org.dhbw.mosbach.ai.cmd.util.CmdConfig;
7 |
8 | import java.io.IOException;
9 | import java.time.LocalDateTime;
10 |
11 | /**
12 | * @author 6694964
13 | * @version 1.1
14 | */
15 | public class LocalDateTimeSerializer extends JsonSerializer In the case of a failure of an atomic test, this method will be called
89 | * with the same {@code Description} passed to
90 | * {@link #testStarted(Description)}, from the same thread that called
91 | * {@link #testStarted(Description)}.
92 | *
93 | * In the case of a listener throwing an exception, this will be called with
94 | * a {@code Description} of {@link Description#TEST_MECHANISM}, and may be called
95 | * on an arbitrary thread.
96 | *
97 | * @param failure describes the test that failed and the exception that was thrown
98 | */
99 | @Override
100 | public void testFailure(Failure failure) throws Exception {
101 | testPrinter.printTestFailure(testNumber.get(), failure);
102 | }
103 |
104 | /**
105 | * Called when an atomic test flags that it assumes a condition that is
106 | * false
107 | *
108 | * @param failure describes the test that failed and the
109 | * {@link org.junit.AssumptionViolatedException} that was thrown
110 | */
111 | @Override
112 | public void testAssumptionFailure(Failure failure) {
113 | testPrinter.printTestAssumptionFailure(testNumber.get(), failure);
114 | }
115 |
116 | /**
117 | * Called when a test will not be run, generally because a test method is annotated
118 | * with {@link org.junit.Ignore}.
119 | *
120 | * @param description describes the test that will not be run
121 | */
122 | @Override
123 | public void testIgnored(Description description) throws Exception {
124 | testPrinter.printTestIgnored(testNumber.get(), description);
125 | testNumber.incrementAndGet();
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/test/java/org/dhbw/mosbach/ai/cmd/extension/listener/log/LogPrefix.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.extension.listener.log;
2 |
3 | public enum LogPrefix {
4 | INFO("INFO"),
5 | ERROR("ERROR");
6 |
7 | private String prefix;
8 |
9 | private LogPrefix(String prefix) {
10 | this.prefix = prefix;
11 | }
12 |
13 | @Override
14 | public String toString() {
15 | return "[" + prefix + "] ";
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/java/org/dhbw/mosbach/ai/cmd/extension/listener/log/TestLogger.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.extension.listener.log;
2 |
3 | import org.junit.runner.notification.Failure;
4 |
5 | public class TestLogger {
6 |
7 | public void info(String message) {
8 | System.out.println(LogPrefix.INFO + message);
9 | }
10 |
11 | public void info(String format, Object... args) {
12 | info(String.format(format, args));
13 | }
14 |
15 | public void error(String message) {
16 | System.err.println(LogPrefix.ERROR + message);
17 | }
18 |
19 | public void error(String format, Object... args) {
20 | error(String.format(format, args));
21 | }
22 |
23 | public void stackTrace(Failure failure) {
24 | String trace = failure.getTrace();
25 | error(trace.replaceAll("\n", "\n" + LogPrefix.ERROR));
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/java/org/dhbw/mosbach/ai/cmd/extension/listener/result/ResultAccumulator.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.extension.listener.result;
2 |
3 | import org.junit.runner.notification.Failure;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 | import java.util.concurrent.atomic.AtomicInteger;
8 |
9 | public class ResultAccumulator {
10 |
11 | private AtomicInteger runCount = new AtomicInteger(0);
12 | private AtomicInteger failureCount = new AtomicInteger(0);
13 | private AtomicInteger ignoreCount = new AtomicInteger(0);
14 |
15 | private List
39 | *
41 | *
42 | * The actual investigation of document access occurs in the document access service
43 | * itself because it is the task of the service rather than the validation to examine
44 | * whether the user has access to a specific document. This validation only takes care
45 | * of the request which contains the document id.
46 | *
47 | * The {@code validate} method follows the principle of error handling descending from
48 | * the Go programming language. Accordingly, a potential error in the validation is
49 | * returned as an unsuccessful {@link ValidationResult} using a sufficient error message
50 | * rather than throwing an exception which requires additional mappers in the services
51 | * to handle those exceptions. A successful validation is returned in the same manner
52 | * as a successful validation result.
53 | *
54 | * In any case, the method should accept a non-null payload and return a non-null
55 | * validation result. In case the payload should be {@code null} this method should
56 | * answer with an unsuccessful validation result containing an internal server error
57 | * response.
58 | *
59 | * @param model the provided non-null document access model from the document service
60 | * @return a non-null validation result indicating if the document access validation
61 | * was successful or not
62 | * @see ModelValidation#validate(Payload)
63 | * @see org.dhbw.mosbach.ai.cmd.services.DocumentService
64 | */
65 | @Override
66 | @NotNull
67 | public ValidationResult validate(@NotNull DocumentAccessModel model) {
68 | if (model == null) {
69 | return ValidationResult.response(new InternalServerError("DocumentAccessModel is null"));
70 | }
71 |
72 | final int documentId = model.getDocumentId();
73 |
74 | final ValidationResult documentExistenceCheck = basicDocumentValidation.checkDocumentExists(documentId);
75 | if (documentExistenceCheck.isInvalid()) {
76 | return documentExistenceCheck;
77 | }
78 |
79 | foundDocument = basicDocumentValidation.getFoundDocument();
80 |
81 | return ValidationResult.success("Access to this document may be checked");
82 | }
83 |
84 | public Doc getFoundDocument() {
85 | return foundDocument;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/org/dhbw/mosbach/ai/cmd/util/CmdConfig.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.util;
2 |
3 | import java.time.format.DateTimeFormatter;
4 |
5 | /**
6 | * Configuration for certain globally reused variables
7 | *
8 | * @author 3040018
9 | */
10 | public abstract class CmdConfig {
11 |
12 | private CmdConfig() {
13 | }
14 |
15 | /**
16 | * Session attribute name and form parameter name for storing user name
17 | */
18 | public static final String SESSION_USERNAME = "username";
19 |
20 | /**
21 | * Session attribute name for storing user id
22 | */
23 | public static final String SESSION_USERID = "userId";
24 |
25 | /**
26 | * Session attribute to show if a user is logged in or not
27 | */
28 | public static final String SESSION_IS_LOGGED_IN = "isLoggedIn";
29 |
30 | /**
31 | * Session attribute to store a user object
32 | */
33 | public static final String SESSION_USER = "user";
34 |
35 | /**
36 | * Unit name for JPA / JTA
37 | */
38 | public static final String JPA_UNIT_NAME = "cmd";
39 |
40 | /**
41 | * Hashing algorithm for the doc content and history database table
42 | */
43 | public static final String HASH_DOC_CONTENT = "SHA-1";
44 |
45 | /**
46 | * Date Formatter used to format dates in date fields of API responses
47 | */
48 | public static final DateTimeFormatter API_DATE_FORMATTER = DateTimeFormatter.ISO_DATE_TIME;
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/org/dhbw/mosbach/ai/cmd/util/HasAccess.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.util;
2 |
3 | import com.fasterxml.jackson.annotation.JsonValue;
4 |
5 | /**
6 | * Utility enum to set if a collaborator has access to a doc
7 | *
8 | * @author spa102716
9 | */
10 | public enum HasAccess {
11 |
12 | Y("Y"),
13 | N("N");
14 |
15 | private String hasAccessString;
16 |
17 | private HasAccess(String hasAccessString) {
18 | this.hasAccessString = hasAccessString;
19 | }
20 |
21 | public String getHasAccessString() {
22 | return this.hasAccessString;
23 | }
24 |
25 | @JsonValue
26 | public boolean hasAccess() {
27 | return this.equals(Y);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/org/dhbw/mosbach/ai/cmd/util/MatchTools.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.util;
2 |
3 | import java.util.Objects;
4 |
5 | public final class MatchTools {
6 |
7 | private MatchTools() {
8 | }
9 |
10 | public static String findDisparateMatches(String regex, String potion) {
11 | return Objects.requireNonNull(potion).replaceAll(regex, "");
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/org/dhbw/mosbach/ai/cmd/util/MessageType.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.util;
2 |
3 | /**
4 | * Message types send via web sockets
5 | *
6 | * @author 3040018
7 | */
8 | public enum MessageType {
9 |
10 | Insert("Insert"),
11 | Delete("Delete"),
12 | UserJoined("UserJoined"),
13 | UserLeft("UserLeft"),
14 | ContentInit("ContentInit"),
15 | DocumentTitle("DocumentTitle"),
16 | UsersInit("UsersInit"),
17 | ChatMessage("ChatMessage"),
18 | WrongDocId("WrongDocId");
19 |
20 | private String messageType;
21 |
22 | private MessageType(String messageType) {
23 | this.messageType = messageType;
24 | }
25 |
26 | @Override
27 | public String toString() {
28 | return this.messageType;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/org/dhbw/mosbach/ai/cmd/websocket/MessageDecoder.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.websocket;
2 |
3 | import org.dhbw.mosbach.ai.cmd.crdt.Message;
4 | import org.dhbw.mosbach.ai.cmd.util.MessageType;
5 |
6 | import javax.json.Json;
7 | import javax.json.JsonObject;
8 | import javax.json.JsonReader;
9 | import javax.websocket.DecodeException;
10 | import javax.websocket.Decoder;
11 | import javax.websocket.EndpointConfig;
12 | import java.io.StringReader;
13 |
14 | /**
15 | * Decoder class for messages to use in the web socket endpoint
16 | *
17 | * @author 3040018
18 | */
19 | public class MessageDecoder implements Decoder.Text... classes) {
65 | archive.addClasses(classes);
66 | return this;
67 | }
68 |
69 | public WebArchive packageWebArchive() {
70 | return archive;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/test/java/org/dhbw/mosbach/ai/cmd/test/helper/PasswordGenerator.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.test.helper;
2 |
3 | import java.security.SecureRandom;
4 | import java.util.Random;
5 |
6 | public class PasswordGenerator {
7 |
8 | private static final String ALPHA_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
9 | private static final String ALPHA_LOWER = "abcdefghijklmnopqrstuvwxyz";
10 | private static final String DIGITS = "0123456789";
11 | private static final String SYMBOLS = "^$*.[]{}()?-\"§!@#%&/\\,><':;|_~`";
12 | private static final String CHAR_SET = ALPHA_UPPER + ALPHA_LOWER + DIGITS + SYMBOLS;
13 |
14 | private Random random = new SecureRandom();
15 |
16 | public String generateSecurePassword(int length) {
17 | // Only allow password lengths that are at least 8
18 | if (length < 8) {
19 | throw new IllegalArgumentException("length has to be at least 8, but is " + length);
20 | }
21 |
22 | // Use at least one uppercase and lowercase character, one digit
23 | // and one symbol in the password
24 | char[] password = new char[length];
25 | password[0] = ALPHA_UPPER.charAt(random.nextInt(ALPHA_UPPER.length()));
26 | password[1] = ALPHA_LOWER.charAt(random.nextInt(ALPHA_LOWER.length()));
27 | password[2] = DIGITS.charAt(random.nextInt(DIGITS.length()));
28 | password[3] = SYMBOLS.charAt(random.nextInt(SYMBOLS.length()));
29 |
30 | // Fill the last slots with random characters from the
31 | // whole character set
32 | for (int i = 4; i < length; i++) {
33 | password[i] = CHAR_SET.charAt(random.nextInt(CHAR_SET.length()));
34 | }
35 |
36 | // Shuffle each character
37 | for (int i = 0; i < password.length; i++) {
38 | int pos = random.nextInt(password.length);
39 | char temp = password[i];
40 | password[i] = password[pos];
41 | password[pos] = temp;
42 | }
43 |
44 | return new String(password);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/org/dhbw/mosbach/ai/cmd/test/include/DependencyIncludes.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.test.include;
2 |
3 | public enum DependencyIncludes {
4 | USER_DAO("org.mindrot:jbcrypt:0.4");
5 |
6 | private String[] dependencies;
7 |
8 | private DependencyIncludes(String... dependencies) {
9 | this.dependencies = dependencies;
10 | }
11 |
12 | public String[] getDependencies() {
13 | return dependencies;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/test/java/org/dhbw/mosbach/ai/cmd/test/include/PackageIncludes.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.test.include;
2 |
3 | public enum PackageIncludes {
4 |
5 | AUTHENTICATION_SERVICE(
6 | "org/dhbw/mosbach/ai/cmd/db",
7 | "org/dhbw/mosbach/ai/cmd/model",
8 | "org/dhbw/mosbach/ai/cmd/security",
9 | "org/dhbw/mosbach/ai/cmd/services",
10 | "org/dhbw/mosbach/ai/cmd/session",
11 | "org/dhbw/mosbach/ai/cmd/util",
12 | "org/dhbw/mosbach/ai/cmd/test/config"
13 | ),
14 |
15 | DOCUMENT_SERVICE(
16 | "org/dhbw/mosbach/ai/cmd/db",
17 | "org/dhbw/mosbach/ai/cmd/model",
18 | "org/dhbw/mosbach/ai/cmd/security",
19 | "org/dhbw/mosbach/ai/cmd/services",
20 | "org/dhbw/mosbach/ai/cmd/session",
21 | "org/dhbw/mosbach/ai/cmd/util",
22 | "org/dhbw/mosbach/ai/cmd/test/config"
23 | ),
24 |
25 | COLLABORATOR_SERVICE(
26 | "org/dhbw/mosbach/ai/cmd/db",
27 | "org/dhbw/mosbach/ai/cmd/model",
28 | "org/dhbw/mosbach/ai/cmd/security",
29 | "org/dhbw/mosbach/ai/cmd/services",
30 | "org/dhbw/mosbach/ai/cmd/session",
31 | "org/dhbw/mosbach/ai/cmd/util",
32 | "org/dhbw/mosbach/ai/cmd/test/config"
33 | ),
34 |
35 | USER_DAO(
36 | "org/dhbw/mosbach/ai/cmd/db",
37 | "org/dhbw/mosbach/ai/cmd/model",
38 | "org/dhbw/mosbach/ai/cmd/security",
39 | "org/dhbw/mosbach/ai/cmd/services/serialize",
40 | "org/dhbw/mosbach/ai/cmd/util",
41 | "org/dhbw/mosbach/ai/cmd/test/config",
42 | "org/dhbw/mosbach/ai/cmd/test/helper"
43 | ),
44 |
45 | DOC_DAO(
46 | "org/dhbw/mosbach/ai/cmd/db",
47 | "org/dhbw/mosbach/ai/cmd/model",
48 | "org/dhbw/mosbach/ai/cmd/security",
49 | "org/dhbw/mosbach/ai/cmd/services/serialize",
50 | "org/dhbw/mosbach/ai/cmd/util",
51 | "org/dhbw/mosbach/ai/cmd/test/config"
52 | );
53 |
54 | private String[] packages;
55 |
56 | private PackageIncludes(String... packages) {
57 | this.packages = packages;
58 | }
59 |
60 | public String[] getPackages() {
61 | return packages;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/test/java/org/dhbw/mosbach/ai/cmd/test/resources/Datasets.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.test.resources;
2 |
3 | public final class Datasets {
4 |
5 | private Datasets() {
6 | }
7 |
8 | public static final String DOCUMENTS = "datasets/documents.yml";
9 |
10 | public static final String USERS = "datasets/users.yml";
11 |
12 | public static final String REPOS = "datasets/repos.yml";
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/java/org/dhbw/mosbach/ai/cmd/test/resources/Scripts.java:
--------------------------------------------------------------------------------
1 | package org.dhbw.mosbach.ai.cmd.test.resources;
2 |
3 | public final class Scripts {
4 |
5 | private Scripts() {
6 | }
7 |
8 | public static final String DISABLE_REFERENTIAL_INTEGRITY = "scripts/disableReferentialIntegrity.sql";
9 |
10 | public static final String RESET_TABLE_IDENTITIES = "scripts/resetTableIdentities.sql";
11 | }
12 |
--------------------------------------------------------------------------------
/src/test/resources/META-INF/test-persistence.xml:
--------------------------------------------------------------------------------
1 |
2 |