diff --git a/jdk_21_maven/cs/rest/joinus/.gitattributes b/jdk_21_maven/cs/rest/joinus/.gitattributes new file mode 100644 index 000000000..3b41682ac --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/jdk_21_maven/cs/rest/joinus/.gitignore b/jdk_21_maven/cs/rest/joinus/.gitignore new file mode 100644 index 000000000..2408946f7 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/.gitignore @@ -0,0 +1,37 @@ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +# Datasets +src/main/resources/JoinUs_Dataset/Meetup/*.csv +src/main/resources/JoinUs_Dataset/dataset2_joinUs/*.csv + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/jdk_21_maven/cs/rest/joinus/.mvn/wrapper/maven-wrapper.properties b/jdk_21_maven/cs/rest/joinus/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..8dea6c227 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip diff --git a/jdk_21_maven/cs/rest/joinus/README.md b/jdk_21_maven/cs/rest/joinus/README.md new file mode 100644 index 000000000..69b8a8e59 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/README.md @@ -0,0 +1,3 @@ +# LSMSDB Project Group 29 +* The Java application code can be found under src +* The deployment setup can be found under deploy \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/joinus/deploy/mongodb/compose.yaml b/jdk_21_maven/cs/rest/joinus/deploy/mongodb/compose.yaml new file mode 100644 index 000000000..bea254a48 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/deploy/mongodb/compose.yaml @@ -0,0 +1,23 @@ +services: + mongo: + image: mongo:8.2.4-noble + container_name: "mongo${NODE_ID}" + restart: always + ports: + - "27017:27017" + volumes: + - mongo_data:/data/db + - ./mongo-keyfile:/etc/mongo-keyfile:ro + environment: + MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD} + RS_NAME: ${RS_NAME} + command: > + mongod + --replSet ${RS_NAME} + --bind_ip_all + --auth + --keyFile /etc/mongo-keyfile + +volumes: + mongo_data: diff --git a/jdk_21_maven/cs/rest/joinus/deploy/mongodb/prune_past_events.sh b/jdk_21_maven/cs/rest/joinus/deploy/mongodb/prune_past_events.sh new file mode 100644 index 000000000..d553c91d5 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/deploy/mongodb/prune_past_events.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${MONGODB_URI:?Missing MONGODB_URI}" +: "${MONGODB_DB:?Missing MONGODB_DB}" + +mongosh "$MONGODB_URI" --quiet --eval ' +const dbName = process.env.MONGODB_DB; +const database = db.getSiblingDB(dbName); +const now = new Date(); + +for (const c of ["groups", "members"]) { + database.getCollection(c).updateMany( + { "upcoming_events.event_time": { $lt: now } }, + { $pull: { upcoming_events: { event_time: { $lt: now } } } } + ); +} +' \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/joinus/deploy/mongodb/rs0-setup.sh b/jdk_21_maven/cs/rest/joinus/deploy/mongodb/rs0-setup.sh new file mode 100644 index 000000000..901b73fc4 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/deploy/mongodb/rs0-setup.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ------------------------ CONFIG ------------------------- +RS_NAME="rs0" +MEMBERS=("10.1.1.18:27017" "10.1.1.19:27017" "10.1.1.20:27017") + +ADMIN_USER="mongodb" +ADMIN_PASS="secure_password" + +CONTAINER="mongo1" # container name on VM1 +# --------------------------------------------------------- + +echo "[*] Checking mongod is reachable in container ${CONTAINER}..." +sudo docker exec -i "${CONTAINER}" mongosh --quiet --eval 'db.runCommand({ ping: 1 })' >/dev/null +echo "[+] mongod reachable." + +echo "[*] Initiating replica set (or skipping if already initialized)..." +sudo docker exec -i "${CONTAINER}" mongosh \ + -u "${ADMIN_USER}" -p "${ADMIN_PASS}" --authenticationDatabase admin --quiet --eval " +(function() { + const rsName = '${RS_NAME}'; + const members = [ + { _id: 0, host: '${MEMBERS[0]}' }, + { _id: 1, host: '${MEMBERS[1]}' }, + { _id: 2, host: '${MEMBERS[2]}' }, + ]; + + function sleep(ms) { const t = Date.now(); while (Date.now() - t < ms) {} } + + // If already initialized, replSetGetStatus will succeed. + try { + const st = db.adminCommand({ replSetGetStatus: 1 }); + print('[+] Replica set already initialized (set=' + st.set + '). Skipping rs.initiate().'); + return; + } catch (e) { + const msg = (e && e.message) ? e.message : String(e); + if (!msg.includes('NotYetInitialized') && !msg.includes('no replset config has been received')) { + print('[!] Unexpected error checking replSetGetStatus: ' + msg); + throw e; + } + } + + print('[*] Running rs.initiate() for ' + rsName + ' ...'); + rs.initiate({ _id: rsName, members }); + + // Wait until PRIMARY elected (may not be this node) + print('[*] Waiting for PRIMARY election...'); + const deadline = Date.now() + 180000; + while (Date.now() < deadline) { + const hello = db.adminCommand({ hello: 1 }); + if (hello.setName === rsName && hello.primary) { + print('[+] PRIMARY is: ' + hello.primary); + return; + } + sleep(1000); + } + throw new Error('Timed out waiting for PRIMARY election'); +})(); +" + +echo "[*] Setting default write concern..." +sudo docker exec -i "${CONTAINER}" mongosh \ + -u "${ADMIN_USER}" -p "${ADMIN_PASS}" --authenticationDatabase admin --quiet --eval " +(function() { + const adminDb = db.getSiblingDB('admin'); + + // Set default RW concern (majority writes) + print('[*] Setting default writeConcern to majority...'); + const res = adminDb.runCommand({ + setDefaultRWConcern: 1, + defaultWriteConcern: { w: 'majority' } + }); + if (res.ok !== 1) throw new Error('setDefaultRWConcern failed: ' + JSON.stringify(res)); + print('[+] Default writeConcern set.'); +})(); +" + +echo "[*] Final replica set status (authenticated):" +sudo docker exec -it "${CONTAINER}" mongosh \ + -u "${ADMIN_USER}" -p "${ADMIN_PASS}" --authenticationDatabase admin --quiet --eval "rs.status()" + +echo +echo "[+] Done." +echo " App connection string example:" +echo " mongodb://${ADMIN_USER}:${ADMIN_PASS}@${MEMBERS[0]},${MEMBERS[1]},${MEMBERS[2]}?replicaSet=${RS_NAME}&authSource=admin&readPreference=nearest" diff --git a/jdk_21_maven/cs/rest/joinus/deploy/mongodb/vm1.env b/jdk_21_maven/cs/rest/joinus/deploy/mongodb/vm1.env new file mode 100644 index 000000000..a5302dd79 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/deploy/mongodb/vm1.env @@ -0,0 +1,14 @@ +NODE_ID=1 + +RS_NAME=rs0 + +# Create admin user on first init +MONGO_INITDB_ROOT_USERNAME=mongodb +MONGO_INITDB_ROOT_PASSWORD=secure_password + + + +# Settings for prune_past_events.sh +# Ensure LF line endings to work in cron job +MONGODB_URI="mongodb://mongodb:secure_password@10.1.1.18:27017,10.1.1.19:27017,10.1.1.20:27017/?replicaSet=rs0&readPreference=primary&retryWrites=true&w=majority" +MONGODB_DB="joinUs" diff --git a/jdk_21_maven/cs/rest/joinus/deploy/mongodb/vm2.env b/jdk_21_maven/cs/rest/joinus/deploy/mongodb/vm2.env new file mode 100644 index 000000000..4be3e12a1 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/deploy/mongodb/vm2.env @@ -0,0 +1,6 @@ +NODE_ID=2 + +RS_NAME=rs0 + +MONGO_INITDB_ROOT_USERNAME= +MONGO_INITDB_ROOT_PASSWORD= diff --git a/jdk_21_maven/cs/rest/joinus/deploy/mongodb/vm3.env b/jdk_21_maven/cs/rest/joinus/deploy/mongodb/vm3.env new file mode 100644 index 000000000..68c141401 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/deploy/mongodb/vm3.env @@ -0,0 +1,6 @@ +NODE_ID=3 + +RS_NAME=rs0 + +MONGO_INITDB_ROOT_USERNAME= +MONGO_INITDB_ROOT_PASSWORD= diff --git a/jdk_21_maven/cs/rest/joinus/deploy/neo4j/compose.yaml b/jdk_21_maven/cs/rest/joinus/deploy/neo4j/compose.yaml new file mode 100644 index 000000000..c2e21f23e --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/deploy/neo4j/compose.yaml @@ -0,0 +1,16 @@ +services: + neo4j: + image: neo4j:2025.12.1 + container_name: neo4j + restart: always + ports: + - "7474:7474" # HTTP (Neo4j Browser) + - "7687:7687" # Bolt + volumes: + - neo4j_data:/data + - ./import:/var/lib/neo4j/import + environment: + NEO4J_AUTH: neo4j/secure_password + +volumes: + neo4j_data: diff --git a/jdk_21_maven/cs/rest/joinus/docker-compose.yaml b/jdk_21_maven/cs/rest/joinus/docker-compose.yaml new file mode 100644 index 000000000..ecd4a2aca --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/docker-compose.yaml @@ -0,0 +1,27 @@ +version: "3.8" + +services: + mongodb: + image: mongo:8.2.3-noble + container_name: mongodb + restart: unless-stopped + ports: + - "27017:27017" + volumes: + - mongodb_data:/data/db + + neo4j: + image: neo4j:2025.12.1 + container_name: neo4j + restart: unless-stopped + ports: + - "7474:7474" # HTTP (Neo4j Browser) + - "7687:7687" # Bolt + environment: + NEO4J_AUTH: neo4j/password + volumes: + - neo4j_data:/data + +volumes: + mongodb_data: + neo4j_data: diff --git a/jdk_21_maven/cs/rest/joinus/mvnw b/jdk_21_maven/cs/rest/joinus/mvnw new file mode 100644 index 000000000..bd8896bf2 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/jdk_21_maven/cs/rest/joinus/mvnw.cmd b/jdk_21_maven/cs/rest/joinus/mvnw.cmd new file mode 100644 index 000000000..92450f932 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/jdk_21_maven/cs/rest/joinus/pom.xml b/jdk_21_maven/cs/rest/joinus/pom.xml new file mode 100644 index 000000000..db300c597 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/pom.xml @@ -0,0 +1,198 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 4.0.1 + + + + com.example + joinUs + 0.0.1-SNAPSHOT + joinUs + JoinUs Project For Large-Scale + + + 21 + 21 + 1.6.3 + + + + + + + com.opencsv + opencsv + 5.12.0 + + + + + + + + + + + + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + + + + + + + + org.springframework.boot + spring-boot-starter-data-neo4j + + + + + + + + + + + org.springframework.boot + spring-boot-starter-webmvc + + + + + + + + + + + org.projectlombok + lombok + + provided + + + + + org.mapstruct + mapstruct + ${mapstruct.version} + compile + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 3.0.0 + + + + + + + + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-security-test + test + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + + com.jcraft + jsch + 0.1.55 + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/JoinUsApplication.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/JoinUsApplication.java new file mode 100644 index 000000000..7a0c4eebf --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/JoinUsApplication.java @@ -0,0 +1,39 @@ +package com.example.joinUs; + +import com.example.joinUs.repository.UserNeo4JRepository; +import com.example.joinUs.repository.UserRepository; +import com.example.joinUs.service.EventService; +import com.example.joinUs.service.GroupService; +import com.example.joinUs.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +public class JoinUsApplication implements CommandLineRunner { + + @Autowired + public EventService eventService; + + @Autowired + public UserService userService; + @Autowired + public UserRepository userRepository; + + @Autowired + public UserNeo4JRepository userNeo4JRepository; + + @Autowired + public GroupService groupService; + + public static void main(String[] args) { + SpringApplication.run(JoinUsApplication.class, args); + } + + @Override + public void run(String... args) throws Exception { + + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/Utils.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/Utils.java new file mode 100644 index 000000000..5241f360a --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/Utils.java @@ -0,0 +1,122 @@ +package com.example.joinUs; + +import com.example.joinUs.model.mongodb.User; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.server.ResponseStatusException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + + +public class Utils { + + public static boolean isNullOrEmpty(T object) { + if (object == null) return true; + + else if (object instanceof String str) { + return str.isEmpty() || str.isBlank(); + } else if (object instanceof Collection) { + Collection collection = (Collection) object; + return collection.isEmpty(); + } + + return false; + } + + public static User getUserFromContext() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || !authentication.isAuthenticated()) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User is not authenticated in the application"); + } + return (User) authentication.getPrincipal(); + } + + public static void checkPropertiesForCreateUpdate(Object... objects) { + for (Object object : objects) { + if (Utils.isNullOrEmpty(object)) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "property not provided"); + } + } + } + + private static Iterable intersectionOf2(Iterable first, Iterable second) { + + ArrayList result = new ArrayList<>(); + Iterator firstIterator = first.iterator(); + while (firstIterator.hasNext()) { + T firstElement = firstIterator.next(); + Iterator secondIterator = second.iterator(); + while (secondIterator.hasNext()) { + T secondElement = secondIterator.next(); + if (secondElement == firstElement) { + result.add(firstElement); + break; + } + } + } + + return result; + } + + public static ArrayList intersection(Iterable... iterable) { + + Iterable result = iterable[0]; + ArrayList list = new ArrayList<>(); + + for (int i = 1; i < iterable.length; i++) { + result = intersectionOf2(result, iterable[i]); + } + Iterator iterator = result.iterator(); + while (iterator.hasNext()) list.add(iterator.next()); + + return list; + } + + public static String getEventProjection() { + return "{'event_id':1,'event_name':1,'venue.city.name':1,'member_count':1,'creator_group':1,'event_time':1,'fee.amount':1}"; + } + + public static String getGroupProjection() { + return "{'group_id':1,'group_name':1,'upcoming_events':1,'city.name':1','category.name':1,'member_count':1,'event_count':1}"; + } + + public static R wrapFunction(Function method, T input) { + try { + return method.apply(input); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + + public static T wrapFunction(Supplier method) { + try { + return method.get(); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + + public static void wrapFunction(Consumer method, T input) { + try { + method.accept(input); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + + public static R wrapFunction(BiFunction method, T firstArg, U secondArg) { + try { + return method.apply(firstArg, secondArg); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + } + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/authorization/RestAccessDeniedHandler.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/authorization/RestAccessDeniedHandler.java new file mode 100644 index 000000000..7c30bc203 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/authorization/RestAccessDeniedHandler.java @@ -0,0 +1,33 @@ +package com.example.joinUs.authorization; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + + +@Component +public class RestAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle( + HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException accessDeniedException + ) throws IOException { + System.out.println(accessDeniedException.getMessage()); + + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.setContentType("application/json"); + + response.getWriter().write(""" + { + "error": "Forbidden", + "message": "You do not have permission to access this endpoint" + } + """); + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/authorization/RestAuthenticationEntryPoint.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/authorization/RestAuthenticationEntryPoint.java new file mode 100644 index 000000000..ad4712c89 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/authorization/RestAuthenticationEntryPoint.java @@ -0,0 +1,30 @@ +package com.example.joinUs.authorization; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; + + +@Component +public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); + + System.out.println(authException.getMessage()); + response.getWriter().write(""" + { + "error": "Unauthorized", + "message": "Please login to access this endpoint" + } + """); + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/config/MainConfiguration.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/config/MainConfiguration.java new file mode 100644 index 000000000..0492e6454 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/config/MainConfiguration.java @@ -0,0 +1,25 @@ +package com.example.joinUs.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mapping.model.FieldNamingStrategy; +import org.springframework.data.mapping.model.SnakeCaseFieldNamingStrategy; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; + + +@Configuration +public class MainConfiguration { + + @Bean + public FieldNamingStrategy fieldNamingStrategy() { + return new SnakeCaseFieldNamingStrategy(); + } + + @Bean + public MongoMappingContext mongoMappingContext(FieldNamingStrategy fieldNamingStrategy) { + MongoMappingContext context = new MongoMappingContext(); + context.setFieldNamingStrategy(fieldNamingStrategy); + return context; + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/config/SecurityConfiguration.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/config/SecurityConfiguration.java new file mode 100644 index 000000000..d581f6e68 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/config/SecurityConfiguration.java @@ -0,0 +1,91 @@ +package com.example.joinUs.config; + +import com.example.joinUs.authorization.RestAccessDeniedHandler; +import com.example.joinUs.authorization.RestAuthenticationEntryPoint; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsPasswordService; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +public class SecurityConfiguration { + + @Autowired + UserDetailsService userDetailsService; + + @Autowired + RestAuthenticationEntryPoint restAuthenticationEntryPoint; + + @Autowired + RestAccessDeniedHandler restAccessDeniedHandler; + + // Configure HTTP security + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.csrf(csrf -> csrf.disable()).sessionManagement(sm -> + sm.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) + ).exceptionHandling(ex -> ex.authenticationEntryPoint(restAuthenticationEntryPoint) + .accessDeniedHandler(restAccessDeniedHandler)) + .authenticationProvider(authenticationProvider()) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/recommendations", "/user/*").authenticated() + .requestMatchers("/user/**").authenticated() + .requestMatchers(HttpMethod.POST, "/groups/**", "/events/**").authenticated() + .requestMatchers(HttpMethod.PUT, "/groups/**", "/events/**").authenticated() + .requestMatchers(HttpMethod.PATCH, "/groups/**", "/events/**").authenticated() + .requestMatchers(HttpMethod.DELETE, "/groups/**", "/events/**").authenticated() + .requestMatchers("/admin/**").hasRole("ADMIN") + .anyRequest().permitAll() + ) + .httpBasic(e -> e.disable()) + .logout(logout -> logout + .logoutUrl("/logout") + .logoutSuccessHandler((req, res, auth) -> { + res.setStatus(HttpServletResponse.SC_OK); + res.getWriter().write("Logged out successfully"); + }) + ) + ; + + return http.build(); + } + + // Password encoder for secure password storage + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); // Use BCrypt for password encoding + } + + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(userDetailsService); + authenticationProvider.setUserDetailsPasswordService(UserDetailsPasswordService.NOOP); + authenticationProvider.setPasswordEncoder(passwordEncoder()); + return authenticationProvider; + } + + @Bean + public AuthenticationManager authenticationManager( + AuthenticationConfiguration config + ) throws Exception { + return config.getAuthenticationManager(); + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/AdminController.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/AdminController.java new file mode 100644 index 000000000..13f6b1b33 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/AdminController.java @@ -0,0 +1,97 @@ +package com.example.joinUs.controller; + +import com.example.joinUs.dto.GroupCommunityDTO; +import com.example.joinUs.dto.analytics.*; +import com.example.joinUs.service.AdminService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + + +/** + * Admin analytics & graph endpoints. + * + * Protect this controller in SecurityConfig: .requestMatchers("/admin/**").hasRole("ADMIN") + */ +@RestController +@RequestMapping("/admin") +@Tag(name = "Admin", description = "Admin-only analytics ") +public class AdminController { + + @Autowired + private AdminService adminService; + + @GetMapping("/analytics/users/activity-score") + @Operation( + summary = "Top users by activity score", + description = "Returns the most active users based on a computed activity score (for example: eventCount + groupCount + other signals)." + ) + public ResponseEntity> topUsersByActivityScore( + @RequestParam(defaultValue = "10") int limit + ) { + return ResponseEntity.ok(adminService.topUsersByActivityScore(limit)); + } + + @GetMapping("/analytics/cities/activity") + @Operation( + summary = "Most active cities", + description = "Ranks cities by activityRatio (activeMembers/totalMembers). A user is considered active if they have at least one upcoming event." + ) + public ResponseEntity> mostActiveCities( + @RequestParam(defaultValue = "10") int limit + ) { + // NOTE: your service currently ignores the limit (repository method returns fixed top 10). + // Keep the param for swagger/API consistency; optionally update the aggregation pipeline to accept ?0. + return ResponseEntity.ok(adminService.mostActiveCities(limit)); + } + +// @GetMapping("/analytics/cities/groups-last-10-years") +// @Operation( +// summary = "Top cities by groups created in last 10 years", +// description = "Counts how many groups were created per city during the last 10 years and returns the highest-ranking cities. Helps identify geographic growth." +// ) +// public ResponseEntity> topCitiesByGroupsLast10Years( +// @RequestParam(defaultValue = "20") int limit +// ) { +// return ResponseEntity.ok(adminService.topCitiesByGroupsLast10Years(limit)); +// } + + @GetMapping("/analytics/topics/trending-per-city") + @Operation( + summary = "Top trending topics per city", + description = "For each city, returns the top topics found in users' profiles, along with how many users have each topic. topicCount controls how many topics per city are returned." + ) + public ResponseEntity> topTrendingTopicsPerCity( + @RequestParam(defaultValue = "5") int topicCount + ) { + return ResponseEntity.ok(adminService.topTrendingTopicsPerCity(topicCount)); + } + + @GetMapping("/analytics/events/paid-vs-free") + @Operation( + summary = "Paid vs Free event popularity", + description = "Compares FREE vs PAID events using totalAttendance, eventsCount, and avgAttendance. An event is FREE if fee is missing/null or fee.amount is 0." + ) + public ResponseEntity paidVsFreePopularity() { + return ResponseEntity.ok(adminService.paidVsFreePopularity()); + } + + @GetMapping("/graph/groups/communities") + @Operation( + summary = "Find group communities (Neo4j)", + description = "Finds related group communities using Neo4j by measuring how many members groups share. sharedMembers is the minimum overlap; limit caps the results." + ) + public ResponseEntity> findGroupCommunities( + @RequestParam(defaultValue = "5") int sharedMembers, + @RequestParam(defaultValue = "20") int limit + ) { + return ResponseEntity.ok(adminService.findGroupCommunities(sharedMembers, limit)); + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/AuthController.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/AuthController.java new file mode 100644 index 000000000..b0dca07b8 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/AuthController.java @@ -0,0 +1,77 @@ +package com.example.joinUs.controller; + +import com.example.joinUs.dto.LoginForm; +import com.example.joinUs.dto.ResponseMessage; +import com.example.joinUs.dto.UserDTO; +import com.example.joinUs.service.UserService; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +@Tag(name = "Auth") +@RestController +public class AuthController { + + @Autowired + UserDetailsService userDetailsService; + + @Autowired + UserService userService; + + @Autowired + AuthenticationManager authManager; + + @GetMapping("/") + public void redirect(HttpServletResponse response) { + + try { + // response.sendRedirect("/login"); + response.sendRedirect("/swagger-ui/index.html"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginForm req, HttpServletRequest request) { + + SecurityContext securityContext = userService.loginUser(req); + if (securityContext != null) { + request.getSession(true).setAttribute("SPRING_SECURITY_CONTEXT", securityContext); + return ResponseEntity.ok(new ResponseMessage("success", "Login of the user was successful ")); + } else { + SecurityContextHolder.clearContext(); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseMessage("failure", + "Your login failed ")); + } + } + + @PostMapping("/logout") + public ResponseEntity logout(HttpServletRequest request) { + HttpSession session = request.getSession(false); + if (session != null) { + session.invalidate(); + } + SecurityContextHolder.clearContext(); + return ResponseEntity.ok(new ResponseMessage("success", "Your logout was successful")); + } + + @PostMapping("/register") + public ResponseEntity register(HttpServletRequest request, @RequestBody UserDTO userDTO) { + SecurityContext securityContext = userService.registerUser(userDTO); + request.getSession(true).setAttribute("SPRING_SECURITY_CONTEXT", securityContext); + return ResponseEntity.ok(new ResponseMessage("success", "Your registration was successful ")); + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/EventController.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/EventController.java new file mode 100644 index 000000000..2798ae63e --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/EventController.java @@ -0,0 +1,153 @@ +package com.example.joinUs.controller; + +import com.example.joinUs.dto.EventDTO; +import com.example.joinUs.dto.summary.EventSummaryDTO; +import com.example.joinUs.service.EventService; +import com.example.joinUs.service.UserService; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Date; + +@Tag(name = "Events") +@RestController() +@RequestMapping("/events") +public class EventController { + + @Autowired + private EventService eventService; + + @Autowired + private UserService userService; + + @GetMapping("") + public Page getAllEvents( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "100") int size) { + return eventService.getAllEvents(page, size); + } + + @GetMapping("/{id}") + public EventDTO getEventById(@PathVariable String id) { + return eventService.getEventById(id); + } + + @PostMapping("") + public ResponseEntity createEvent(@RequestBody EventDTO eventDTO) { + return ResponseEntity.ok(eventService.createEvent(eventDTO)); + } + + @PatchMapping("/{id}") + public ResponseEntity editEvent(@PathVariable String id, @RequestBody EventDTO eventDTO) { + + return ResponseEntity.ok(eventService.updateEvent(eventDTO, id)); + } + + @PatchMapping("/{id}/attend") + public ResponseEntity attendEvent(@PathVariable String id) { + + return ResponseEntity.ok(eventService.attendEvent(id)); + } + + @PatchMapping("/{id}/revokeAttendance") + public ResponseEntity revokeAttendance(@PathVariable String id) { + return ResponseEntity.ok(eventService.revokeAttendance(id)); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteEvent(@PathVariable String id) { + eventService.deleteEvent(id); + } + + @GetMapping("/search") + public Page searchEvents( + @RequestParam(required = false) String name, + @RequestParam(required = false) String city, + @RequestParam(required = false) String category, + @RequestParam(required = false) Integer minMembers, + @RequestParam(required = false) Integer maxMembers, + @Parameter(example = "2026-12-15T01:45:30Z") + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + Date fromDate, + @Parameter(example = "2027-12-15T01:45:30Z") + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) + Date toDate, + @RequestParam(required = false) Integer maxFee, + @RequestParam(defaultValue = "0") Integer page, + @RequestParam(defaultValue = "100") Integer pageSize + + ) { + return eventService.search( + name, + city, + category, + minMembers, + maxMembers, + fromDate, + toDate, + maxFee, + page, + pageSize + ); + } + + // @GetMapping("/searchByName") + // public Page searchByName(@RequestParam String name, + // @RequestParam(defaultValue = "0") int page, + // @RequestParam(defaultValue = "100") int size ) { + // return eventService.findByEventNameContainingIgnoreCase(name,page,size); + // } + // + // @GetMapping("/filterByCity") + // public Page filterByCity(@RequestParam String city, + // @RequestParam(defaultValue = "0") int page, + // @RequestParam(defaultValue = "100") int size ) { + // return eventService.filterByCity(city,page,size); + // } + // + // @GetMapping("/filterByCategory") + // public Page filterByCategory(@RequestParam String category, + // @RequestParam(defaultValue = "0") int page, + // @RequestParam(defaultValue = "100") int size ) { + // return eventService.filterByCategory(category,page,size); + // } + // + // @GetMapping("/filterByMembersCount") + // public Page filterByMembersCount( + // @RequestParam int min, + // @RequestParam int max, + // @RequestParam int page, + // @RequestParam(defaultValue = "100") int size + // ) { + // return eventService.filterByMemberCount(min, max,page,size); + // } + // + // @GetMapping("/filterByDate") + // public Page filterByDate( //TODO Change Date's type to include only the Date and not the hh:mm:ssss + // @RequestParam(required = false) + // @Parameter(example = "2026-03-25T23:00:00.000") + // LocalDateTime from, + // @RequestParam(required = false) + // @Parameter(example = "2027-01-25T23:00:00.000") + // LocalDateTime to, + // @RequestParam(defaultValue = "0") int page, + // @RequestParam(defaultValue = "100") int size + // ) { + // return eventService.filterByDateRange(from, to,page,size); + // } + // + // @GetMapping("/filterByFee") + // public Page filterByFee(@RequestParam int maxFee, + // @RequestParam(defaultValue = "0") int page, + // @RequestParam(defaultValue = "100") int size ) { + // return eventService.filterByMaxFee(maxFee,page,size); + // } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/GroupController.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/GroupController.java new file mode 100644 index 000000000..38000e2bc --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/GroupController.java @@ -0,0 +1,116 @@ +package com.example.joinUs.controller; + +import com.example.joinUs.dto.GroupDTO; +import com.example.joinUs.dto.summary.EventSummaryDTO; +import com.example.joinUs.dto.summary.GroupSummaryDTO; +import com.example.joinUs.service.GroupService; +import com.example.joinUs.service.UserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "Groups") +@RestController +@RequestMapping("/groups") +public class GroupController { + + @Autowired + private GroupService groupService; + + @Autowired + private UserService userService; + + // READ: list all groups + @GetMapping + public Page getAllGroups(@RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "100") int size) { + return groupService.getAllGroups(page, size); + } + + // READ: get one group by groupId + @GetMapping("/{id}") + public GroupDTO getGroupById(@PathVariable String id) { + return groupService.getGroupById(id); + } + + @PostMapping("/{groupId}/join") + public ResponseEntity joinGroup(@PathVariable String groupId) { + return ResponseEntity.ok(groupService.joinGroup(groupId)); + } + + // Leave group + @PostMapping("/{groupId}/leave") + public ResponseEntity leaveGroup(@PathVariable String groupId) { + return ResponseEntity.ok(groupService.leaveGroup(groupId)); + } + + // Add organizer + @PostMapping("/{groupId}/organizers/{organizerId}") + public ResponseEntity addOrganizer( + @PathVariable String groupId, + @PathVariable String organizerId + ) { + return ResponseEntity.ok(groupService.addOrganizer(groupId, organizerId)); + } + + // CREATE: create a new group + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public ResponseEntity createGroup(@RequestBody GroupDTO groupDTO) { + return ResponseEntity.ok(groupService.createGroup(groupDTO)); + } + + // UPDATE (partial): update an existing group + @PatchMapping("/{id}") + public ResponseEntity updateGroup(@PathVariable String id, @RequestBody GroupDTO groupDTO) { + + return ResponseEntity.ok(groupService.updateGroup(id, groupDTO)); + } + + @GetMapping("/{groupId}/events") + @Operation(summary = "Get all events (historical and upcoming) organized by a group") + public List getEventsOrganizedByGroup( + @PathVariable String groupId + ) { + return groupService.findAllEventOrganizedByGroup(groupId); + } + + @GetMapping("/search") + public Page searchGroups( + @RequestParam(required = false) String name, + @RequestParam(required = false) String city, + @RequestParam(required = false) String category, + @RequestParam(required = false) Integer minMembers, + @RequestParam(required = false) Integer maxMembers, + @RequestParam(required = false) Integer minEvents, + @RequestParam(required = false) Integer maxEvents, + @RequestParam(defaultValue = "0") Integer page, + @RequestParam(defaultValue = "100") Integer pageSize + + ) { + return groupService.search( + name, + city, + category, + minMembers, + maxMembers, + minEvents, + maxEvents, + page, + pageSize + ); + } + + // DELETE: delete a group + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteGroup(@PathVariable String id) { + groupService.deleteGroup(id); + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/RecommendationController.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/RecommendationController.java new file mode 100644 index 000000000..16a890955 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/RecommendationController.java @@ -0,0 +1,77 @@ +package com.example.joinUs.controller; + +import com.example.joinUs.dto.GroupDTO; +import com.example.joinUs.dto.summary.EventSummaryDTO; +import com.example.joinUs.service.RecommendationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Tag(name = "Recommendations") +@RestController +@RequestMapping("/recommendations") +public class RecommendationController { + + @Autowired + private RecommendationService recommendationService; + + /* ---------------- GROUP RECOMMENDATIONS ---------------- */ + + @Operation( + summary = "Recommend groups joined by members similar to the authenticated user" + ) + @GetMapping("/groups/by-similar-members") + public List recommendGroupsBySimilarMembers( + @RequestParam(defaultValue = "10") int limit + ) { + return recommendationService.recommendGroupsBySimilarMembers(limit); + } + + @Operation( + summary = "Recommend groups based on the user's topics" + ) + @GetMapping("/groups/by-topics") + public List recommendGroupsByTopics( + @RequestParam(defaultValue = "10") int limit + ) { + return recommendationService.recommendGroupsByTopics(limit); + } + + /* ---------------- EVENT RECOMMENDATIONS ---------------- */ + + @Operation( + summary = "Recommend events related to the user's group topics" + ) + @GetMapping("/events/by-group-topics") + public List recommendEventsBySharedGroupTopics( + @RequestParam(defaultValue = "10") int limit + ) { + return recommendationService.recommendEventsBySharedGroupTopics(limit); + } + + @Operation( + summary = "Recommend events attended by members with similar attendance patterns" + ) + @GetMapping("/events/by-members") + public List recommendEventsByMembers( + @RequestParam(defaultValue = "10") int limit + ) { + return recommendationService.recommendEventsByMembers(limit); + } + + @Operation( + summary = "Recommend events attended by members of the user's groups" + ) + @GetMapping("/events/by-peer-group-attendance") + public List recommendEventsByPeerGroupAttendance( + @RequestParam(defaultValue = "10") int limit + ) { + return recommendationService.recommendEventsByPeerGroupAttendance(limit); + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/UserController.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/UserController.java new file mode 100644 index 000000000..3d38b1967 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/controller/UserController.java @@ -0,0 +1,66 @@ +package com.example.joinUs.controller; + +import com.example.joinUs.dto.GroupDTO; +import com.example.joinUs.dto.UserDTO; +import com.example.joinUs.dto.summary.EventSummaryDTO; +import com.example.joinUs.service.UserService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "User") +@RestController +@RequestMapping("/user") +public class UserController { + + @Autowired + private UserService userService; + + @GetMapping("/profile") + public ResponseEntity getUserProfile() { + return ResponseEntity.ok(userService.getUserProfile()); + } + + @PutMapping("/profile") + public ResponseEntity editUserProfile(@RequestBody UserDTO user) { + return ResponseEntity.ok().body(userService.editUserProfile(user)); + } + + @DeleteMapping("/profile") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteUserProfile(HttpServletRequest request) { + userService.deleteProfile(); + HttpSession session = request.getSession(false); + if (session != null) { + session.invalidate(); + } + SecurityContextHolder.clearContext(); + } + + /** + * Get all events the authenticated user is attending + */ + @GetMapping("/allEvents") + @Operation(summary = "Get all events (upcoming and historical) of the authenticated user") + public List getMyEvents() { + return userService.findAllEvents(); + } + + /** + * Get all groups the authenticated user belongs to + */ + @GetMapping("/allGroups") + @Operation(summary = "Get all groups (upcoming and historical) of the authenticated user") + public List getMyGroups() { + return userService.findAllGroups(); + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/CategoryDTO.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/CategoryDTO.java new file mode 100644 index 000000000..e96886a32 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/CategoryDTO.java @@ -0,0 +1,19 @@ +package com.example.joinUs.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CategoryDTO { + + private String categoryId; + private String name; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/CityDTO.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/CityDTO.java new file mode 100644 index 000000000..48aab1646 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/CityDTO.java @@ -0,0 +1,27 @@ +package com.example.joinUs.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CityDTO { + + private String id; + private String name; + + private String country; + private String state; + + private String latitude; + private String longitude; + private String distance; + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/EventDTO.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/EventDTO.java new file mode 100644 index 000000000..285720f94 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/EventDTO.java @@ -0,0 +1,41 @@ +package com.example.joinUs.dto; + +import com.example.joinUs.model.embedded.GroupEmbedded; +import com.example.joinUs.model.mongodb.Category; +import com.example.joinUs.model.mongodb.Fee; +import com.example.joinUs.model.mongodb.Venue; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class EventDTO { + + private String id; + private String eventName; + private String description; + + private Date created; + private Date eventTime; + private Date updated; + + private Integer duration; + + private Fee fee; + private Venue venue; + private Category category; + + private Integer memberCount; + + private GroupEmbedded creatorGroup; + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/FeeDTO.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/FeeDTO.java new file mode 100644 index 000000000..261d40ded --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/FeeDTO.java @@ -0,0 +1,25 @@ +package com.example.joinUs.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class FeeDTO { + + private String accepts; + private Integer amount; + private String description; + private Boolean isRequired; + + public static FeeDTO getDefaultDTO() { + return new FeeDTO("others", 0, "per person", false); + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/GroupCommunityDTO.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/GroupCommunityDTO.java new file mode 100644 index 000000000..4e33fbffa --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/GroupCommunityDTO.java @@ -0,0 +1,20 @@ +package com.example.joinUs.dto; + +import com.example.joinUs.model.neo4j.GroupNeo4J; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class GroupCommunityDTO { + + private GroupNeo4J group1; + private GroupNeo4J group2; + private long sharedMembers; +} + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/GroupDTO.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/GroupDTO.java new file mode 100644 index 000000000..5b84de97a --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/GroupDTO.java @@ -0,0 +1,44 @@ +package com.example.joinUs.dto; + +import com.example.joinUs.model.embedded.CityEmbedded; +import com.example.joinUs.model.embedded.EventEmbedded; +import com.example.joinUs.model.embedded.TopicEmbedded; +import com.example.joinUs.model.embedded.UserEmbedded; +import com.example.joinUs.model.mongodb.Category; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; +import java.util.List; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GroupDTO { + + private String id; + private String description; + private String groupName; + private Date created; + + private CityEmbedded city; + private Category category; + + private Integer memberCount; + private Integer eventCount; + + private List organizers; + + private List upcomingEvents; + private List topics; + + private GroupPhotoDTO groupPhoto; + +} + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/GroupPhotoDTO.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/GroupPhotoDTO.java new file mode 100644 index 000000000..b87ac26b0 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/GroupPhotoDTO.java @@ -0,0 +1,21 @@ +package com.example.joinUs.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GroupPhotoDTO { + + private String highresLink; + private String photoLink; + private String thumbLink; + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/LoginForm.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/LoginForm.java new file mode 100644 index 000000000..8b0302956 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/LoginForm.java @@ -0,0 +1,7 @@ +package com.example.joinUs.dto; + +public class LoginForm { + + public String userid; + public String password; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/ResponseMessage.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/ResponseMessage.java new file mode 100644 index 000000000..58c916fd7 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/ResponseMessage.java @@ -0,0 +1,12 @@ +package com.example.joinUs.dto; + +public class ResponseMessage { + + public String status; + public String message; + + public ResponseMessage(String status, String message) { + this.status = status; + this.message = message; + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/TopicDTO.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/TopicDTO.java new file mode 100644 index 000000000..6178296dd --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/TopicDTO.java @@ -0,0 +1,23 @@ +package com.example.joinUs.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TopicDTO { + + private String id; + private String topicName; + private String link; + private String description; + private String urlkey; + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/UserDTO.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/UserDTO.java new file mode 100644 index 000000000..a97d7751d --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/UserDTO.java @@ -0,0 +1,37 @@ +package com.example.joinUs.dto; + +import com.example.joinUs.model.embedded.CityEmbedded; +import com.example.joinUs.model.embedded.EventEmbedded; +import com.example.joinUs.model.embedded.TopicEmbedded; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class UserDTO { + + private String id; + private String memberName; + private String password; // TODO discuss + + private CityEmbedded city; + + private String bio; + + private List topics; + + private Integer eventCount; + private Integer groupCount; + + private List upcomingEvents; + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/VenueDTO.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/VenueDTO.java new file mode 100644 index 000000000..01364651c --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/VenueDTO.java @@ -0,0 +1,22 @@ +package com.example.joinUs.dto; + +import com.example.joinUs.model.embedded.CityEmbedded; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class VenueDTO { + + private CityEmbedded city; + private String address1; + private String address2; + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/ActivityScorePerUserAnalytic.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/ActivityScorePerUserAnalytic.java new file mode 100644 index 000000000..fe2c456db --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/ActivityScorePerUserAnalytic.java @@ -0,0 +1,18 @@ +package com.example.joinUs.dto.analytics; + +import lombok.Data; +import org.springframework.data.mongodb.core.mapping.Field; + + +@Data +public class ActivityScorePerUserAnalytic { + + @Field("userId") + public String userId; + + @Field("userName") + public String userName; + + @Field("activityScore") + public Long activityScore; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/CityActivityAnalytic.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/CityActivityAnalytic.java new file mode 100644 index 000000000..d936f7b4c --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/CityActivityAnalytic.java @@ -0,0 +1,27 @@ +package com.example.joinUs.dto.analytics; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.mongodb.core.mapping.Field; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CityActivityAnalytic { + + @Field("city") + private String city; + + @Field("totalMembers") + private Integer totalMembers; + + @Field("activeMembers") + private Integer activeMembers; + + @Field("activityRatio") + private Double activityRatio; +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/GroupsPerCityAnalytic.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/GroupsPerCityAnalytic.java new file mode 100644 index 000000000..205481bc4 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/GroupsPerCityAnalytic.java @@ -0,0 +1,18 @@ +package com.example.joinUs.dto.analytics; + +import lombok.Data; +import org.springframework.data.mongodb.core.mapping.Field; + + +@Data +public class GroupsPerCityAnalytic { + + @Field("_id") + public String _id; + + @Field("groupsCreated") + public Integer groupsCreated; + + @Field("city") + public String city; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/GroupsPerOrganizerAnalytic.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/GroupsPerOrganizerAnalytic.java new file mode 100644 index 000000000..494106522 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/GroupsPerOrganizerAnalytic.java @@ -0,0 +1,18 @@ +package com.example.joinUs.dto.analytics; + +import lombok.Data; +import org.springframework.data.mongodb.core.mapping.Field; + + +@Data +public class GroupsPerOrganizerAnalytic { + + @Field("userId") + public String userId; + + @Field("organizerName") + public String organizerName; + + @Field("groupsOrganized") + public Integer groupsOrganized; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/GroupsPerTopicAnalytic.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/GroupsPerTopicAnalytic.java new file mode 100644 index 000000000..4a7e7d9e2 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/GroupsPerTopicAnalytic.java @@ -0,0 +1,16 @@ +package com.example.joinUs.dto.analytics; + +import lombok.Data; +import org.springframework.data.mongodb.core.mapping.Field; + + +@Data +public class GroupsPerTopicAnalytic { + + @Field("topic") + public String topic; + + @Field("groupsCount") + public Integer groupsCount; + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/PaidVsFreeEventAnalytic.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/PaidVsFreeEventAnalytic.java new file mode 100644 index 000000000..90acf6735 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/PaidVsFreeEventAnalytic.java @@ -0,0 +1,21 @@ +package com.example.joinUs.dto.analytics; + +import lombok.Data; +import org.springframework.data.mongodb.core.mapping.Field; + + +@Data +public class PaidVsFreeEventAnalytic { + + @Field("eventsCount") + public Integer eventsCount; + + @Field("totalAttendance") + public Integer totalAttendance; + + @Field("avgAttendance") + public Integer avgAttendance; + + @Field("type") + public String type; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/TopicPerUserAnalytic.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/TopicPerUserAnalytic.java new file mode 100644 index 000000000..19dc6b65a --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/TopicPerUserAnalytic.java @@ -0,0 +1,15 @@ +package com.example.joinUs.dto.analytics; + +import lombok.Data; +import org.springframework.data.mongodb.core.mapping.Field; + + +@Data +public class TopicPerUserAnalytic { + + @Field("topic") + public String topic; + + @Field("usersCount") + public Integer usersCount; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/TrendingTopicPerCityAnalytic.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/TrendingTopicPerCityAnalytic.java new file mode 100644 index 000000000..0c5332715 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/TrendingTopicPerCityAnalytic.java @@ -0,0 +1,19 @@ +package com.example.joinUs.dto.analytics; + +import lombok.Data; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +import java.util.List; + + +@Data +@Document +public class TrendingTopicPerCityAnalytic { + + @Field("city") + public String city; + + @Field("topTopics") + public List topTopics; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/UsersPerCityAnalytic.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/UsersPerCityAnalytic.java new file mode 100644 index 000000000..b42c0c8c0 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/analytics/UsersPerCityAnalytic.java @@ -0,0 +1,15 @@ +package com.example.joinUs.dto.analytics; + +import lombok.Data; +import org.springframework.data.mongodb.core.mapping.Field; + + +@Data +public class UsersPerCityAnalytic { + + @Field("city") + public String city; + + @Field("usersCount") + public Integer usersCount; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/summary/EventSummaryDTO.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/summary/EventSummaryDTO.java new file mode 100644 index 000000000..7222c7ec2 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/summary/EventSummaryDTO.java @@ -0,0 +1,32 @@ +package com.example.joinUs.dto.summary; + +import com.example.joinUs.model.embedded.GroupEmbedded; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + + +// return "{'event_id':1,'event_name':1,'venue.city.name':1, +// 'member_count':1,'creator_group':1,'event_time':1}"; +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class EventSummaryDTO { + + private String id; + private String eventName; + private Date eventTime; + private Date updated; + private Integer memberCount; + private Integer feeAmount; + private String venueCityName; + private String venueAddress1; + //TODO add venueAddress1 in the mapper + private GroupEmbedded creatorGroup; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/summary/GroupSummaryDTO.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/summary/GroupSummaryDTO.java new file mode 100644 index 000000000..8f8df65f8 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/dto/summary/GroupSummaryDTO.java @@ -0,0 +1,30 @@ +package com.example.joinUs.dto.summary; + +import com.example.joinUs.model.embedded.EventEmbedded; +import com.example.joinUs.model.embedded.UserEmbedded; +import com.example.joinUs.model.mongodb.Category; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GroupSummaryDTO { + + private String id; + private String groupName; + private Category category; + private String cityName; + private List organizers; + private List upcomingEvents; + private Integer memberCount; + private Integer eventCount; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/exceptions/GlobalExceptionHandler.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/exceptions/GlobalExceptionHandler.java new file mode 100644 index 000000000..223b8d782 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/exceptions/GlobalExceptionHandler.java @@ -0,0 +1,19 @@ +package com.example.joinUs.exceptions; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.server.ResponseStatusException; + + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(ResponseStatusException.class) + public ResponseEntity handleInvalidEvent(ResponseStatusException ex) { + return ResponseEntity.status(ex.getStatusCode()) + .body(ex.getBody()); + } + +} + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/CategoryMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/CategoryMapper.java new file mode 100644 index 000000000..1fb9ba9e5 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/CategoryMapper.java @@ -0,0 +1,19 @@ +package com.example.joinUs.mapping; + +import com.example.joinUs.dto.CategoryDTO; +import com.example.joinUs.model.mongodb.Category; +import org.mapstruct.Mapper; + +import java.util.List; + + +@Mapper(config = CentralMappingConfig.class) +public interface CategoryMapper { + + CategoryDTO toDTO(Category category); + + List toDTOs(List categories); + + Category toEntity(CategoryDTO dto); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/CentralMappingConfig.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/CentralMappingConfig.java new file mode 100644 index 000000000..6b3aba4bb --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/CentralMappingConfig.java @@ -0,0 +1,13 @@ +package com.example.joinUs.mapping; + +import org.mapstruct.MapperConfig; +import org.mapstruct.ReportingPolicy; + + +@MapperConfig( + componentModel = "spring", + unmappedTargetPolicy = ReportingPolicy.ERROR +) +public interface CentralMappingConfig { + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/CityMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/CityMapper.java new file mode 100644 index 000000000..05d5c19c3 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/CityMapper.java @@ -0,0 +1,22 @@ +package com.example.joinUs.mapping; + +import com.example.joinUs.dto.CityDTO; +import com.example.joinUs.model.embedded.CityEmbedded; +import com.example.joinUs.model.mongodb.City; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + + +@Mapper(config = CentralMappingConfig.class) +public interface CityMapper { + + CityDTO toDTO(City city); + + City toEntity(CityDTO dto); + + @Mapping(target = "cityId", source = "id") + @Mapping(target = "name", source = "name") + @Mapping(target = "country", source = "country") + @Mapping(target = "state", source = "state") + CityEmbedded toEmbedded(City city); +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/EventMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/EventMapper.java new file mode 100644 index 000000000..1948d049f --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/EventMapper.java @@ -0,0 +1,75 @@ +package com.example.joinUs.mapping; + +//import com.example.joinUs.dto.EventDTO; +//import com.example.joinUs.model.mongodb.Event; + +//package com.example.joinUs.dto; + +import com.example.joinUs.dto.EventDTO; +import com.example.joinUs.dto.summary.EventSummaryDTO; +import com.example.joinUs.mapping.embedded.GroupEmbeddedMapper; +import com.example.joinUs.mapping.embedded.TopicEmbeddedMapper; +import com.example.joinUs.model.mongodb.Event; +import com.example.joinUs.model.neo4j.EventNeo4J; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.Date; + + +@Mapper(config = CentralMappingConfig.class, uses = { + CategoryMapper.class + , GroupEmbeddedMapper.class + , VenueMapper.class, TopicEmbeddedMapper.class } + // TODO +) +public interface EventMapper { + + @Mapping(target = "id", source = "id") + EventDTO toDTO(Event event); + + @Mapping(target = "id", source = "id") + Event toEntity(EventDTO dto); + + @Mapping(source = "feeAmount", target = "feeAmount") + @Mapping(source = "cityName", target = "venueCityName") + @Mapping(source = "eventId", target = "id") + @Mapping(target = "updated", ignore = true) + @Mapping(target = "memberCount", ignore = true) + @Mapping(target = "creatorGroup", ignore = true) + EventSummaryDTO toDTOFromNeo4j(EventNeo4J event); + + @Mapping(target = "feeAmount", source = "fee.amount") + @Mapping(target = "cityName", source = "venue.city.name") + @Mapping(target = "groupName", source = "creatorGroup.groupName") + @Mapping(target = "groupId", source = "creatorGroup.groupId") + @Mapping(target = "eventId", source = "id") + @Mapping(target = "venueAddress1", source = "venue.address1") + EventNeo4J toNeo4jEntity(EventDTO event); + + // @Mapping(source = "feeAmount", target = "fee.amount") + // @Mapping(source = "cityName", target = "venue.city.name") + // @Mapping(target = "created", ignore = true) + // @Mapping(target = "duration", ignore = true) + // @Mapping(target = "category", ignore = true) + // @Mapping(target = "memberCount", ignore = true) + // @Mapping(source = "groupName", target = "creatorGroup.groupName") + // @Mapping(source = "groupId", target = "creatorGroup.groupId") + // @Mapping(source = "eventId", target = "id") + // @Mapping(source = "eventName", target = "eventName") + // @Mapping(source = "eventTime", target = "eventTime") + // @Mapping(source = "description", target = "description") + // @Mapping(target = "venueAddress1", source = "venue.address1") + // List toDTOsFromNeo4j(List events); + + default Date map(OffsetDateTime value) { + return value == null ? null : Date.from(value.toInstant()); + } + + default OffsetDateTime map(Date value) { + return value == null ? null : OffsetDateTime.ofInstant(value.toInstant(), ZoneId.systemDefault()); + } + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/FeeMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/FeeMapper.java new file mode 100644 index 000000000..8f90e8f22 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/FeeMapper.java @@ -0,0 +1,14 @@ +package com.example.joinUs.mapping; + +import com.example.joinUs.dto.FeeDTO; +import com.example.joinUs.model.mongodb.Fee; +import org.mapstruct.Mapper; + + +@Mapper(config = CentralMappingConfig.class) +public interface FeeMapper { + + FeeDTO toDTO(Fee fee); + + Fee toEntity(FeeDTO dto); +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/GroupMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/GroupMapper.java new file mode 100644 index 000000000..bddf3a402 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/GroupMapper.java @@ -0,0 +1,52 @@ +package com.example.joinUs.mapping; + +import com.example.joinUs.dto.GroupDTO; +import com.example.joinUs.mapping.embedded.EventEmbeddedMapper; +import com.example.joinUs.mapping.embedded.UserEmbeddedMapper; +import com.example.joinUs.model.mongodb.Group; +import com.example.joinUs.model.neo4j.GroupNeo4J; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueMappingStrategy; + +import java.util.List; + + +@Mapper(config = CentralMappingConfig.class, + uses = { CityMapper.class, CategoryMapper.class, EventEmbeddedMapper.class, + GroupPhotoMapper.class, UserEmbeddedMapper.class }, + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL // TODO others also +) +public interface GroupMapper { + + // @Mapping(target = "groupId",source = "id") + // @Mapping(source = "id", ignore = true) + GroupDTO toDTO(Group group); + + // @Mapping(target = "groupId",source = "id") + //@Mapping(target = "id", ignore = true) + List toDTOs(List groups); + + // Ignore Mongo internal id because the DTO doesn't carry it. + // @Mapping(target = "id",source = "groupId") + // @Mapping(target = "id", ignore = true) + Group toEntity(GroupDTO dto); + + @Mapping(target = "groupId", source = "id") + @Mapping(target = "cityName", source = "city.name") + @Mapping(target = "categoryName", source = "category.name") + GroupNeo4J toNeo4JEntity(GroupDTO dto); + + @Mapping(target = "id", source = "groupId") + @Mapping(target = "city.name", source = "cityName") + @Mapping(target = "category.name", source = "categoryName") + @Mapping(target = "created", ignore = true) + @Mapping(target = "memberCount", ignore = true) + @Mapping(target = "eventCount", ignore = true) + @Mapping(target = "organizers", ignore = true) + @Mapping(target = "upcomingEvents", ignore = true) + @Mapping(target = "topics", ignore = true) + @Mapping(target = "groupPhoto", ignore = true) + GroupDTO toNeo4JDTO(GroupNeo4J groupNeo4J); + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/GroupPhotoMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/GroupPhotoMapper.java new file mode 100644 index 000000000..debfc7b3e --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/GroupPhotoMapper.java @@ -0,0 +1,15 @@ +package com.example.joinUs.mapping; + +import com.example.joinUs.dto.GroupPhotoDTO; +import com.example.joinUs.model.mongodb.GroupPhoto; +import org.mapstruct.Mapper; + + +@Mapper(config = CentralMappingConfig.class) +public interface GroupPhotoMapper { + + GroupPhotoDTO toDTO(GroupPhoto groupPhoto); + + GroupPhoto toEntity(GroupPhotoDTO dto); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/TopicMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/TopicMapper.java new file mode 100644 index 000000000..8bd952ec8 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/TopicMapper.java @@ -0,0 +1,20 @@ +package com.example.joinUs.mapping; + +import com.example.joinUs.dto.TopicDTO; +import com.example.joinUs.model.mongodb.Topic; +import org.mapstruct.Mapper; + +import java.util.List; + + +@Mapper(config = CentralMappingConfig.class) +public interface TopicMapper { + + TopicDTO toDTO(Topic topic); + + // @Mapping(target = "id", ignore = true) + Topic toEntity(TopicDTO dto); + + List toDTOs(List topics); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/UserMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/UserMapper.java new file mode 100644 index 000000000..10521f917 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/UserMapper.java @@ -0,0 +1,53 @@ +package com.example.joinUs.mapping; + +import com.example.joinUs.dto.UserDTO; +import com.example.joinUs.mapping.embedded.EventEmbeddedMapper; +import com.example.joinUs.mapping.embedded.TopicEmbeddedMapper; +import com.example.joinUs.model.mongodb.User; +import com.example.joinUs.model.neo4j.UserNeo4J; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + + +@Mapper(config = CentralMappingConfig.class, uses = { CityMapper.class, + EventEmbeddedMapper.class, TopicEmbeddedMapper.class }) +public interface UserMapper { + + @Mapping(target = "id", source = "id") + UserDTO toDTO(User user); + + // @Mapping(target = "roles",ignore = true) + // @Mapping(target = "authorities",ignore = true) + // @Mapping(target = "id", ignore = true) + @Mapping(target = "isAdmin", ignore = true) + @Mapping(target = "id", source = "id") + User toEntity(UserDTO dto); + + @Mapping(target = "id", source = "memberId") + @Mapping(target = "memberName", source = "memberName") + @Mapping(target = "city.name", source = "cityName") + @Mapping(target = "topics", ignore = true) + @Mapping(target = "bio", ignore = true) + @Mapping(target = "password", ignore = true) + @Mapping(target = "groupCount", ignore = true) + @Mapping(target = "eventCount", ignore = true) + @Mapping(target = "upcomingEvents", ignore = true) + UserDTO toNeo4jDTO(UserNeo4J user); + + @Mapping(source = "id", target = "memberId") + @Mapping(source = "memberName", target = "memberName") + @Mapping(source = "city.name", target = "cityName") + UserNeo4J toNeo4jEntity(UserDTO dto); + + // + /* + @Mapping(target = "topics", ignore = true) + @Mapping(target = "bio", ignore = true) + @Mapping(target = "password", ignore = true) + @Mapping(target = "groupCount", ignore = true) + @Mapping(target = "eventCount", ignore = true) + @Mapping(target = "upcomingEvents", ignore = true) + + */ + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/VenueMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/VenueMapper.java new file mode 100644 index 000000000..192aac610 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/VenueMapper.java @@ -0,0 +1,15 @@ +package com.example.joinUs.mapping; + +import com.example.joinUs.dto.VenueDTO; +import com.example.joinUs.model.mongodb.Venue; +import org.mapstruct.Mapper; + + +@Mapper(config = CentralMappingConfig.class, uses = { CityMapper.class }) +public interface VenueMapper { + + VenueDTO toDTO(Venue venue); + + Venue toEntity(VenueDTO dto); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/embedded/EventEmbeddedMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/embedded/EventEmbeddedMapper.java new file mode 100644 index 000000000..0554b1a5a --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/embedded/EventEmbeddedMapper.java @@ -0,0 +1,25 @@ +package com.example.joinUs.mapping.embedded; + +import com.example.joinUs.mapping.CentralMappingConfig; +import com.example.joinUs.model.embedded.EventEmbedded; +import com.example.joinUs.model.mongodb.Event; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import java.util.List; + + +@Mapper(config = CentralMappingConfig.class, uses = { GroupEmbeddedMapper.class }) +public interface EventEmbeddedMapper { + + @Mapping(target = "eventId", source = "id") + @Mapping(source = "eventName", target = "eventName") + @Mapping(source = "eventTime", target = "eventTime") + EventEmbedded toDTO(Event event); + + @Mapping(target = "eventId", source = "id") + @Mapping(source = "eventName", target = "eventName") + @Mapping(source = "eventTime", target = "eventTime") + List toDTOs(List events); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/embedded/GroupEmbeddedMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/embedded/GroupEmbeddedMapper.java new file mode 100644 index 000000000..b43f02dcf --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/embedded/GroupEmbeddedMapper.java @@ -0,0 +1,34 @@ +package com.example.joinUs.mapping.embedded; + +import com.example.joinUs.mapping.CentralMappingConfig; +import com.example.joinUs.model.embedded.GroupEmbedded; +import com.example.joinUs.model.mongodb.Group; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + + +@Mapper(config = CentralMappingConfig.class) +public interface GroupEmbeddedMapper { + + // // Explicitly ignored fields + + @Mapping(source = "id", target = "groupId") + @Mapping(target = "groupName", source = "groupName") + GroupEmbedded toDTO(Group group); + + // @Mapping(target = "link", ignore = true) + // @Mapping(target = "timezone", ignore = true) + // @Mapping(target = "created", ignore = true) + // @Mapping(target = "city", ignore = true) + // @Mapping(target = "categories", ignore = true) + // @Mapping(target = "memberCount", ignore = true) + // @Mapping(target = "eventCount", ignore = true) + // @Mapping(target = "description", ignore = true) + // @Mapping(target = "groupPhoto", ignore = true) + // @Mapping(target = "organizers", ignore = true) + // @Mapping(target = "upcomingEvents", ignore = true) + // @Mapping(target = "id", source = "groupId") + // @Mapping(target = "groupName", source = "groupName") + // Group toEntity(GroupEmbedded groupEmbedded); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/embedded/TopicEmbeddedMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/embedded/TopicEmbeddedMapper.java new file mode 100644 index 000000000..6bced1406 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/embedded/TopicEmbeddedMapper.java @@ -0,0 +1,29 @@ +package com.example.joinUs.mapping.embedded; + +import com.example.joinUs.mapping.CentralMappingConfig; +import com.example.joinUs.model.embedded.TopicEmbedded; +import com.example.joinUs.model.mongodb.Topic; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import java.util.List; + + +@Mapper(config = CentralMappingConfig.class) +public interface TopicEmbeddedMapper { + + @Mapping(source = "id", target = "topicId") + @Mapping(source = "topicName", target = "topicName") + TopicEmbedded toDTO(Topic topic); + + @Mapping(source = "id", target = "topicId") + @Mapping(source = "topicName", target = "topicName") + List toDTOs(List topics); + + // @Mapping(target = "id", ignore = true) + // @Mapping(target = "link", ignore = true) + // @Mapping(target = "description", ignore = true) + // @Mapping(target = "urlkey", ignore = true) + // Topic toEntity(TopicEmbedded dto); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/embedded/UserEmbeddedMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/embedded/UserEmbeddedMapper.java new file mode 100644 index 000000000..e1d7aae3b --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/embedded/UserEmbeddedMapper.java @@ -0,0 +1,34 @@ +package com.example.joinUs.mapping.embedded; + +import com.example.joinUs.mapping.CentralMappingConfig; +import com.example.joinUs.mapping.UserMapper; +import com.example.joinUs.model.embedded.UserEmbedded; +import com.example.joinUs.model.mongodb.User; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import java.util.List; + + +@Mapper(config = CentralMappingConfig.class, uses = { UserMapper.class }) +public interface UserEmbeddedMapper { + + @Mapping(target = "memberId", source = "id") + @Mapping(target = "memberName", source = "memberName") + UserEmbedded toDTO(User user); + + @Mapping(target = "memberId", source = "id") + @Mapping(target = "memberName", source = "memberName") + List toDTO(List user); + + @Mapping(target = "topics", ignore = true) + @Mapping(target = "bio", ignore = true) + @Mapping(target = "city", ignore = true) + @Mapping(target = "groupCount", ignore = true) + @Mapping(target = "eventCount", ignore = true) + @Mapping(target = "upcomingEvents", ignore = true) + @Mapping(target = "password", ignore = true) + @Mapping(target = "isAdmin", ignore = true) + @Mapping(target = "id", source = "memberId") + User toEntity(UserEmbedded userEmbedded); +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/summary/EventSummaryMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/summary/EventSummaryMapper.java new file mode 100644 index 000000000..040f14156 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/summary/EventSummaryMapper.java @@ -0,0 +1,36 @@ +package com.example.joinUs.mapping.summary; + +import com.example.joinUs.dto.summary.EventSummaryDTO; +import com.example.joinUs.mapping.CentralMappingConfig; +import com.example.joinUs.mapping.FeeMapper; +import com.example.joinUs.mapping.VenueMapper; +import com.example.joinUs.mapping.embedded.GroupEmbeddedMapper; +import com.example.joinUs.model.mongodb.Event; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import java.util.List; + + +@Mapper(config = CentralMappingConfig.class, uses = { + VenueMapper.class, FeeMapper.class, + GroupEmbeddedMapper.class }) +public interface EventSummaryMapper { + + @Mapping(source = "fee.amount", target = "feeAmount") + @Mapping(source = "venue.city.name", target = "venueCityName") + @Mapping(source = "venue.address1", target = "venueAddress1") + @Mapping(source = "id", target = "id") + // @Mapping(source = "creatorGroup.id", target = "creatorGroup.groupId") + // @Mapping(source = "creatorGroup.groupName", target = "creatorGroup.groupName") + EventSummaryDTO toDTO(Event event); + + @Mapping(source = "fee.amount", target = "feeAmount") + @Mapping(source = "venue.city.name", target = "venueCityName") + @Mapping(source = "venue.address1", target = "venueAddress1") + @Mapping(source = "id", target = "id") + // @Mapping(source = "creatorGroup.id", target = "creatorGroup.groupId") + // @Mapping(source = "creatorGroup.groupName", target = "creatorGroup.groupName") + List toDTOs(List events); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/summary/GroupSummaryMapper.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/summary/GroupSummaryMapper.java new file mode 100644 index 000000000..b85d95b66 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/mapping/summary/GroupSummaryMapper.java @@ -0,0 +1,57 @@ +package com.example.joinUs.mapping.summary; + +import com.example.joinUs.dto.CategoryDTO; +import com.example.joinUs.dto.summary.GroupSummaryDTO; +import com.example.joinUs.mapping.CentralMappingConfig; +import com.example.joinUs.mapping.UserMapper; +import com.example.joinUs.mapping.embedded.EventEmbeddedMapper; +import com.example.joinUs.mapping.embedded.GroupEmbeddedMapper; +import com.example.joinUs.mapping.embedded.UserEmbeddedMapper; +import com.example.joinUs.model.mongodb.Group; +import com.example.joinUs.model.neo4j.GroupNeo4J; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import java.util.List; + + +@Mapper(config = CentralMappingConfig.class, uses = { + UserEmbeddedMapper.class, EventEmbeddedMapper.class, GroupEmbeddedMapper.class, + UserMapper.class }) + +public interface GroupSummaryMapper { + + @Mapping(source = "city.name", target = "cityName") + @Mapping(source = "id", target = "id") + @Mapping(source = "organizers", target = "organizers") + GroupSummaryDTO toDTO(Group group); + + //groupId + @Mapping(source = "city.name", target = "cityName") + @Mapping(source = "id", target = "id") + @Mapping(source = "organizers", target = "organizers") + List toDTOs(List group); + + @Mapping(source = "groupId", target = "id") + @Mapping(source = "groupName", target = "groupName") + @Mapping(source = "cityName", target = "cityName") + @Mapping(source = "categoryName", target = "category.name") + @Mapping(target = "upcomingEvents", ignore = true) + @Mapping(target = "memberCount", ignore = true) + @Mapping(target = "eventCount", ignore = true) + @Mapping(target = "organizers", ignore = true) + GroupSummaryDTO toDTOFromNeo4j(GroupNeo4J group); + + List toDTOsFromNeo4j(List groupNeo4JS); + + default List mapCategory(String categoryName) { + if (categoryName == null) return null; + + return List.of( + CategoryDTO.builder() + .name(categoryName) + .build() + ); + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/CityEmbedded.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/CityEmbedded.java new file mode 100644 index 000000000..1542ef42e --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/CityEmbedded.java @@ -0,0 +1,19 @@ +package com.example.joinUs.model.embedded; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class CityEmbedded { + + private String cityId; + private String name; + private String country; + private String state; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/EventEmbedded.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/EventEmbedded.java new file mode 100644 index 000000000..c681639c7 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/EventEmbedded.java @@ -0,0 +1,21 @@ +package com.example.joinUs.model.embedded; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +//@JsonInclude(JsonInclude.Include.NON_NULL) +public class EventEmbedded { + + private String eventId; + private String eventName; + private Date eventTime; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/GroupEmbedded.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/GroupEmbedded.java new file mode 100644 index 000000000..634725c7a --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/GroupEmbedded.java @@ -0,0 +1,18 @@ +package com.example.joinUs.model.embedded; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +//@JsonInclude(JsonInclude.Include.NON_NULL) +public class GroupEmbedded { + + private String groupId; + private String groupName; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/TopicEmbedded.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/TopicEmbedded.java new file mode 100644 index 000000000..428781d3f --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/TopicEmbedded.java @@ -0,0 +1,18 @@ +package com.example.joinUs.model.embedded; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +//@JsonInclude(JsonInclude.Include.NON_NULL) +public class TopicEmbedded { + + private String topicId; + private String topicName; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/UserEmbedded.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/UserEmbedded.java new file mode 100644 index 000000000..fc8686391 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/embedded/UserEmbedded.java @@ -0,0 +1,18 @@ +package com.example.joinUs.model.embedded; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +//@JsonInclude(JsonInclude.Include.NON_NULL) +public class UserEmbedded { + + private String memberId; + private String memberName; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Category.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Category.java new file mode 100644 index 000000000..1e6abe4fb --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Category.java @@ -0,0 +1,17 @@ +package com.example.joinUs.model.mongodb; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class Category { + + private String categoryId; + private String name; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/City.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/City.java new file mode 100644 index 000000000..e6bc861bd --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/City.java @@ -0,0 +1,33 @@ +package com.example.joinUs.model.mongodb; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Document(collection = "cities") +@JsonInclude(JsonInclude.Include.NON_NULL) +public class City { + + @Id + @Field("_id") + private String id; + private String name; + + private String country; + private String state; + + private String latitude; + private String longitude; + private String distance; + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Event.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Event.java new file mode 100644 index 000000000..afb27f8cc --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Event.java @@ -0,0 +1,44 @@ +package com.example.joinUs.model.mongodb; + +import com.example.joinUs.model.embedded.GroupEmbedded; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +import java.util.Date; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Document(collection = "events") +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Event { + + @Id + @Indexed(unique = true) + private String id; + private String description; + private String eventName; + + private Date created; + private Date eventTime; + private Date updated; + + private Integer duration; + private Fee fee; + private Venue venue; + private Category category; + private Integer memberCount; + @Field("creator_group") + private GroupEmbedded creatorGroup; + +} + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Fee.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Fee.java new file mode 100644 index 000000000..6f0109cf4 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Fee.java @@ -0,0 +1,23 @@ +package com.example.joinUs.model.mongodb; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Fee { + + private String accepts; + private Integer amount; + private String description; + private Boolean isRequired; + + public static Fee getDefault() { + return new Fee("others", 0, "per person", false); + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Group.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Group.java new file mode 100644 index 000000000..e0db90627 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Group.java @@ -0,0 +1,63 @@ +package com.example.joinUs.model.mongodb; + +import com.example.joinUs.model.embedded.CityEmbedded; +import com.example.joinUs.model.embedded.EventEmbedded; +import com.example.joinUs.model.embedded.TopicEmbedded; +import com.example.joinUs.model.embedded.UserEmbedded; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.Date; +import java.util.List; + + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Document(collection = "groups") +public class Group { + + @Id + @Indexed(unique = true) + private String id; + + private String description; + private String groupName; + private Date created; + + private CityEmbedded city; + private Category category; + private GroupPhoto groupPhoto; + + private Integer memberCount; + private Integer eventCount; + + private List organizers; + private List upcomingEvents; + private List topics; + + public void removeOrganizerMember(String memberId) { + organizers.removeIf(e -> e.getMemberId().equalsIgnoreCase(memberId)); + } + + public void removeUpcomingEvent(String eventId) { + upcomingEvents.removeIf(e -> e.getEventId().equalsIgnoreCase(eventId)); + } + + public void addUpcomingEvent(EventEmbedded eventEmbedded) { + upcomingEvents.add(eventEmbedded); + } + + public void addOrganizer(UserEmbedded userEmbedded) { + organizers.add(userEmbedded); + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/GroupPhoto.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/GroupPhoto.java new file mode 100644 index 000000000..67e636ec9 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/GroupPhoto.java @@ -0,0 +1,20 @@ +package com.example.joinUs.model.mongodb; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class GroupPhoto { + + private String highresLink; + private String photoLink; + private String thumbLink; +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Topic.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Topic.java new file mode 100644 index 000000000..dc77be3b4 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Topic.java @@ -0,0 +1,30 @@ +package com.example.joinUs.model.mongodb; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Document(collection = "topics") +public class Topic { + + @Id + @Indexed(unique = true) + @Field("_id") + private String id; + + private String topicName; + private String link; + private String description; + private String urlkey; + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/User.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/User.java new file mode 100644 index 000000000..71a9d7939 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/User.java @@ -0,0 +1,102 @@ +package com.example.joinUs.model.mongodb; + +import com.example.joinUs.model.embedded.CityEmbedded; +import com.example.joinUs.model.embedded.EventEmbedded; +import com.example.joinUs.model.embedded.TopicEmbedded; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@Document(collection = "members") +public class User implements UserDetails { + + @Id + @Indexed(unique = true) + private String id; + + @Field("member_name") + private String memberName; + + @Field("city") + private CityEmbedded city; + + @Field("bio") + private String bio; + + @Field("topics") + private List topics; + + @Field("event_count") + private Integer eventCount; + + @Field("group_count") + private Integer groupCount; + + @Field("upcoming_events") + private List upcomingEvents; + + @Field("password") + private String password; + + @Field("isAdmin") + private Boolean isAdmin; + + public void removeUpcomingEvent(String eventId) { + upcomingEvents.removeIf(e -> e.getEventId().equalsIgnoreCase(eventId)); + } + + @Override + public Collection getAuthorities() { + Collection list = new ArrayList<>(); + if (isAdmin) list.add("ROLE_ADMIN"); // TODO also include groups? + return list.stream().map(e -> new SimpleGrantedAuthority(e)).toList(); + } + + @Override + public String getPassword() { + return this.password; + } + + @Override + public String getUsername() { + return this.memberName; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Venue.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Venue.java new file mode 100644 index 000000000..76e6ab238 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/mongodb/Venue.java @@ -0,0 +1,22 @@ +package com.example.joinUs.model.mongodb; + +import com.example.joinUs.model.embedded.CityEmbedded; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Venue { + + private CityEmbedded city; + private String address1; + private String address2; + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/neo4j/EventNeo4J.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/neo4j/EventNeo4J.java new file mode 100644 index 000000000..bff8a38a5 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/neo4j/EventNeo4J.java @@ -0,0 +1,42 @@ +package com.example.joinUs.model.neo4j; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Property; + +import java.time.OffsetDateTime; + + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Node("Event") +public class EventNeo4J { + + @Id + @Property(name = "event_id") + private String eventId; + @Property(name = "event_name") + private String eventName; + @Property(name = "group_name") + private String groupName; + @Property(name = "group_id") + private String groupId; + @Property(name = "description") + private String description; + @Property(name = "event_time") + private OffsetDateTime eventTime; + @Property(name = "fee_amount") + private String feeAmount; + @Property(name = "venue_city") + private String cityName; + @Property(name = "venue_address_1") + private String venueAddress1; + +} + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/neo4j/GroupNeo4J.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/neo4j/GroupNeo4J.java new file mode 100644 index 000000000..677bf4b7e --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/neo4j/GroupNeo4J.java @@ -0,0 +1,32 @@ +package com.example.joinUs.model.neo4j; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Property; + + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Node("Group") +public class GroupNeo4J { + + @Id + @Property("group_id") + private String groupId; + @Property("group_name") + private String groupName; + @Property("description") + private String description; + @Property("city") + private String cityName; + @Property("category_name") + private String categoryName; + +} + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/neo4j/TopicNeo4J.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/neo4j/TopicNeo4J.java new file mode 100644 index 000000000..3e0eeca54 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/neo4j/TopicNeo4J.java @@ -0,0 +1,27 @@ +package com.example.joinUs.model.neo4j; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Property; + + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Node("Topic") +public class TopicNeo4J { + + @Id + @Property(name = "topic_id") + private String topicId; + + @Property(name = "topic_name") + private String topicName; + +} + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/neo4j/UserNeo4J.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/neo4j/UserNeo4J.java new file mode 100644 index 000000000..648dc93b7 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/model/neo4j/UserNeo4J.java @@ -0,0 +1,30 @@ +package com.example.joinUs.model.neo4j; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Property; + + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Node("Member") +public class UserNeo4J { + + @Id + @Property(name = "member_id") + private String memberId; + + @Property(name = "member_name") + private String memberName; + + @Property(name = "city") + private String cityName; + +} + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/CityRepository.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/CityRepository.java new file mode 100644 index 000000000..e90b590b7 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/CityRepository.java @@ -0,0 +1,17 @@ +package com.example.joinUs.repository; + +import com.example.joinUs.model.mongodb.City; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + + +@Repository +public interface CityRepository extends MongoRepository { + + @Query(value = "{_id: ?0}") + City findByCityId(String id); + + City findByName(String name); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/EventNeo4JRepository.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/EventNeo4JRepository.java new file mode 100644 index 000000000..a548cab3a --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/EventNeo4JRepository.java @@ -0,0 +1,107 @@ +package com.example.joinUs.repository; + +import com.example.joinUs.model.neo4j.EventNeo4J; +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.neo4j.repository.query.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + + +@Repository +public interface EventNeo4JRepository extends Neo4jRepository { + + @Query(""" + MATCH (u:Event { event_Id: $eventId }) + DETACH DELETE u + """) + void deleteEvent(String eventId); + + @Query("MATCH ( e: Event {event_id: $eventId}) " + + "MATCH ( m: Member {member_id: $userId}) " + + "MERGE (e)-[:ATTENDS]-> (m) " + + "MERGE (m)-[:ATTENDS]-> (e) ") + void addUserAttending(String eventId, String userId); + + @Query(""" + MATCH (e:Event {event_id: $eventId})-[r1:ATTENDS]->(m:Member {member_id: $userId}) + MATCH (m)-[r2:ATTENDS]->(e) + DELETE r1, r2 + """) + void revokeUserAttending(String eventId, String userId); + + @Query(""" + MATCH (g:Group {group_id: $groupId}) + MATCH (e:Event {event_id: $eventId}) + MERGE (g)-[:ORGANIZES]->(e) + MERGE (e)-[:ORGANIZES]->(g) + """) + void addGroupEventRelation(String groupId, String eventId); + + @Query(""" + MATCH (g:Group {group_id: $groupId})-[r1:ORGANIZES]->(e:Event {event_id: $eventId}) + MATCH (e)-[r2:ORGANIZES]->(g) + DELETE r1, r2 + """) + void removeGroupEventRelation(String groupId, String eventId); + + @Query(""" + MATCH (g:Group {group_id: $groupId})-[:ORGANIZES]-(e:Event) + RETURN e + """) + List findAllEventsOrganizedByGroup(String groupId); + + @Query(""" + MATCH (m:Member {member_id: $memberId})-[:ATTENDS]-(e:Event) + RETURN e + """) + List findAllEventsAttendedByUser(String memberId); + + //(How many users attended the same events as users of a given event) + @Query(""" + MATCH (e:Event {event_id: $eventId}) + <-[:ATTENDS]-(m:Member) + -[:ATTENDS]->(otherEvent:Event) + <-[:ATTENDS]-(otherMember:Member) + WHERE otherMember <> m + RETURN COUNT(DISTINCT otherMember) + """) + long countCoAttendees(String eventId); + + /** + * Recommend events attended by peers (members sharing a group with me) + */ + @Query(""" + MATCH (me:Member {member_id: $memberId})-[:MEMBER_OF]->(g:Group)<-[:MEMBER_OF]-(other:Member)-[:ATTENDS]->(e:Event) + WHERE e.event_time>datetime() AND NOT (me)-[:ATTENDS]->(e) + RETURN DISTINCT e + ORDER BY e.event_time + LIMIT $limit + """) + List recommendEventsByPeerGroupAttendance( + @Param("memberId") String memberId, + @Param("limit") long limit + );//Recommend events that members of the same groups will attend + + @Query(""" + MATCH (me:Member {member_id: $memberId})-[:ATTENDS]->(e1:Event)<-[:ATTENDS]-(other:Member)-[:ATTENDS]->(e2:Event) + WHERE e2.event_time>datetime() AND NOT (me)-[:ATTENDS]->(e2) + WITH e2 AS event, COUNT(DISTINCT other) AS score + ORDER BY score DESC + LIMIT $limit + RETURN event + """) + List recommendEventsByMembers(String memberId, @Param("limit") long limit); + + @Query(""" + MATCH (m:Member {member_id: $memberId})-[:INTERESTED_IN]->(t:Topic)<-[:HAS_TOPIC]-(g:Group)-[:ORGANIZES]->(e:Event) + WHERE e.event_time>datetime() AND NOT (m)-[:ATTENDS]->(e) + WITH e AS event, COUNT(DISTINCT t) AS score + ORDER BY score DESC + LIMIT $limit + RETURN event + """) + List recommendEventsBySharedGroupTopics(String memberId, @Param("limit") long limit); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/EventRepository.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/EventRepository.java new file mode 100644 index 000000000..fffdb3733 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/EventRepository.java @@ -0,0 +1,250 @@ +package com.example.joinUs.repository; + +import com.example.joinUs.dto.EventDTO; +import com.example.joinUs.dto.analytics.PaidVsFreeEventAnalytic; +import com.example.joinUs.model.mongodb.Event; +import org.bson.Document; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.mongodb.repository.Aggregation; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; +import java.util.Optional; + + +@Repository +public interface EventRepository extends MongoRepository { + + String fields = "{'_id':1,'event_name':1,'venue.city.name':1,'member_count':1,'creator_group':1,'event_time':1,'fee.amount':1}"; + + // String eventProjection= "{ $project:"+fields+" }"; + + Optional findById(String id); //TODO check + + boolean existsById(String id); + + void deleteById(String id); + + @Query(value = "{_id:{$ne:null}}", fields = fields) + Page findAllEvents(Pageable pageable); + + @Query(value = "{ _id: ?0 }", fields = fields) + EventDTO findEventWithId(String id); + + // @Query(value = "{_id:{$ne:null}}",fields = fields,count = true) + // Page findAll(Pageable pageable); + + @Query(value = "{'event_name' : { $regex: ?0, $options: 'i' } }", fields = fields) + Page searchByEventName(String name, Pageable pageable); + + // List findByEventNameContainingIgnoreCase(String name); + + @Query(value = "{ 'venue.city.name' : { $regex: ?0, $options: 'i' } }", fields = fields) + Page findByCityName(String cityName, Pageable pageable); + + //{ 'id': 1, 'artists': 1, 'coverURL': 1, 'title': 1 } + @Query(value = "{ 'category.name' : { $regex: ?0, $options: 'i' } }", fields = fields) + Page findByCategoryName(String categoryName, Pageable pageable); + + // @Query(""" + // { + // 'venue.city.name' : ?0, + // 'event_name' : { $regex: ?1, $options: 'i' } + // } + // """) + // List findByCityAndName(String city, String name); + + @Query( + value = "{ 'member_count' : { $gte: ?0, $lte: ?1 } }", + fields = fields, + sort = "{ 'member_count' : -1 }" + ) + Page findByMemberCountBetween(int minMemberCount, int maxMemberCount, Pageable pageable); + + @Query( + value = "{ 'member_count' : { $gte: ?0 } }", + fields = fields, + sort = "{ 'member_count' : -1 }" + ) + Page findByMemberCountGreaterThan(int minMemberCount, Pageable pageable); + + @Query( + value = "{ 'member_count' : { $lte: ?0 } }", + fields = fields, + sort = "{ 'member_count' : -1 }" + ) + Page findByMemberCountLessThan(int maxMemberCount, Pageable pageable); + + @Query( + value = "{ 'event_time' : { $gte: ?0, $lte: ?1 } }", + fields = fields, + sort = "{ 'event_time' : 1 }" + + ) + Page findByEventTimeBetween(LocalDateTime from, LocalDateTime to, Pageable pageable); + + @Query( + value = "{ 'event_time' : { $gte: ?0} }", + fields = fields, + sort = "{ 'event_time' : 1 }" + ) + Page findByEventTimeAfter(LocalDateTime from, Pageable pageable); + + @Query( + value = "{ 'event_time' : { $lte: ?0} }", + fields = fields, + sort = "{ 'event_time' : 1 }" + ) + Page findByEventTimeBefore(LocalDateTime to, Pageable pageable); + + @Query( + value = "{ 'fee.amount' : {$lte:?0} }", + fields = fields, + sort = "{ 'fee.amount' : 1 }" + ) + Page findByEventFeeIsLessOrEqual(int max, Pageable pageable); + + @Query(value = "{'creator_group.id' : ?0 }", fields = fields) + List findEventsByCreatorGroup(String id); + + //Aggregations for the Admin controller + //Count Events per City to see Which cities host the most events + @Aggregation(pipeline = { + "{ $group: { _id: '$creator_group.city.name', eventsCount: { $sum: 1 } } }", + "{ $sort: { eventsCount: -1 } }" + }) + List countEventsByCity(); + + // //How many events are created each month. + // @Aggregation(pipeline = { + // "{ $match: { event_time: { $ne: null } } }", + // "{ $group: { _id: { year: { $year: '$event_time' }, month: { $month: '$event_time' } }, eventsCount: { $sum: 1 } } }", + // "{ $sort: { '_id.year': 1, '_id.month': 1 } }" + // }) + // List countEventsPerMonth(); + + //Trending Categories (Shows which categories are growing or declining.) See which category people attend most + @Aggregation(pipeline = { + "{ $match: { event_time: { $gte: ?1 } } }", + "{ $group: { " + + "_id: '$category.name', " + + "lastWeek: { $sum: { $cond: [ { $gte: ['$event_time', ?0] }, { $ifNull: ['$member_count', 0] }, 0 ] } }, " + + "previousWeek: { $sum: { $cond: [ { $and: [ { $gte: ['$event_time', ?1] }, { $lt: ['$event_time', ?0] } ] }, { $ifNull: ['$member_count', 0] }, 0 ] } } " + + "} }", + "{ $addFields: { delta: { $subtract: ['$lastWeek', '$previousWeek'] } } }", + "{ $sort: { delta: -1 } }" + }) + List trendingCategoriesWeekly(Date lastWeekStart, Date prevWeekStart); + + //Popularity of Paid vs Free Events showing Do people attend paid or free events more? + @Aggregation(pipeline = { + "{ $addFields: { " + + "eventType: { $cond: [ " + + "{ $or: [ { $eq: ['$fee.amount', 0] }, { $eq: ['$fee', null] }, { $eq: ['$fee.amount', null] } ] }, " + + "'FREE', 'PAID' ] }, " + + "attendance: { $ifNull: ['$member_count', 0] } " + + "} }", + + "{ $group: { _id: '$eventType', eventsCount: { $sum: 1 }, totalAttendance: { $sum: '$attendance' }" + + ", avgAttendance: { $avg: '$attendance' } } }", + + "{ $project: { type: '$_id', eventsCount: 1, totalAttendance: 1, avgAttendance: 1, _id: 0 } }", + "{ $sort: { totalAttendance: -1 } }" + }) + List paidVsFreePopularity(); + + //Top cities by upcoming events count (overall activity) + @Aggregation(pipeline = { + "{ $match: { event_time: { $gte: ?0 } } }", + "{ $group: { _id: '$creator_group.city.name', totalUpcomingEvents: { $sum: 1 } } }", + "{ $sort: { totalUpcomingEvents: -1 } }", + "{ $limit: 20 }", + "{ $project: { city: '$_id', totalUpcomingEvents: 1, _id: 0 } }" + }) + List topCitiesByUpcomingEvents(Date now); + + // List findByEventNameContainingIgnoreCase(String name); + + // USING THE AGGREGATION APPROACH FOR THE Queries + // + // @Aggregation( pipeline = { + // "{$match:{'event_name' : { $regex: ?0, $options: 'i' } }}", + // "{$skip: ?1}", + // "{$limit: ?2}", + // eventProjection + // } + // ) + // List searchByEventNameAggregation(String name,int offset,int limit); + // + // @Aggregation(pipeline = { + // "{$match { 'venue.city.name' : ?0 } }", + // "{$skip: ?1}", + // "{$limit: ?2}", + // eventProjection + // } + // ) + // List findByCityNameAggregation(String cityName,int offset,int limit); + // + // + // @Aggregation(pipeline = { + // "{$match : { 'categories.name' : { $regex: ?0, $options: 'i' } } }", + // "{$skip: ?1}", + // "{$limit: ?2}", + // eventProjection + // }) + // List findByCategoryNameAggregation(String categoryName,int offset,int limit); + // + //// @Query(""" + //// { + //// 'venue.city.name' : ?0, + //// 'event_name' : { $regex: ?1, $options: 'i' } + //// } + //// """) + //// List findByCityAndName(String city, String name); + // + // @Aggregation(pipeline = { + // "{$match :{ 'member_count' : { $gte: ?0, $lte: ?1 } } }", + // "{$sort:{ 'member_count':-1 } }", + // "{$skip: ?2}", + // "{$limit: ?3}", + // eventProjection + // } + // ) + // List findByMemberCountBetweenAggregation(int minMemberCount, int maxMemberCount, int offset,int limit); + // + // @Aggregation(pipeline = { + // "{$match: { 'event_time' : { $gte: ?0, $lte: ?1 } } }", + // "{$sort:{ 'event_time' : 1 }}", + // "{$skip: ?2}", + // "{$limit: ?3}", + // eventProjection} + // ) + // List findByEventTimeBetweenAggregation(Date from, Date to, int offset,int limit); + // + // @Aggregation(pipeline = { + // "{$match: { 'fee.amount' : {$lte:?0} } }", + // "{$sort: { 'fee.amount' : 1 } }", + // "{$skip: ?1}", + // "{$limit: ?2}", + // eventProjection + // } + // ) + // List findByEventFeeIsLessOrEqualAggregation(int max, int offset,int limit); + // + // + // + // @Aggregation(pipeline = { + // "{$match: { 'member_count' : { $gte: ?0, $lte: ?1 } }}", + // "{$sort:{'member_count':-1}}", + // "{$skip: ?2}", + // "{$limit: ?3}", + // eventProjection + // }) + // List findSecondWayByMemberCount(int minMemberCount,int maxMemberCount,int offset,int limit); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/GroupCommunityRepository.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/GroupCommunityRepository.java new file mode 100644 index 000000000..908434b1c --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/GroupCommunityRepository.java @@ -0,0 +1,64 @@ +package com.example.joinUs.repository; + +import com.example.joinUs.dto.GroupCommunityDTO; +import com.example.joinUs.model.neo4j.GroupNeo4J; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.neo4j.core.Neo4jClient; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Map; + + +@RequiredArgsConstructor +@Repository +public class GroupCommunityRepository { + + @Autowired + private final Neo4jClient neo4jClient; + + public List findGroupCommunities(long minShared, long limit) { + + String cypher = """ + MATCH (g1:Group)<-[:MEMBER_OF]-(m:Member)-[:MEMBER_OF]->(g2:Group) + WHERE g1 <> g2 AND g1.group_id < g2.group_id + WITH g1, g2, count(DISTINCT m) AS sharedMembers + WHERE sharedMembers >= $minShared + RETURN g1 AS group1, g2 AS group2, sharedMembers AS sharedMembers + ORDER BY sharedMembers DESC + LIMIT $limit + """; + + return neo4jClient.query(cypher) + .bindAll(Map.of("minShared", minShared, "limit", limit)) + .fetchAs(GroupCommunityDTO.class) + .mappedBy((typeSystem, record) -> { + var n1 = record.get("group1").asNode(); + var n2 = record.get("group2").asNode(); + + GroupNeo4J g1 = GroupNeo4J.builder() + .groupId(n1.get("group_id").asString()) + .groupName(n1.get("group_name").asString(null)) + .description(n1.get("description").asString(null)) + .cityName(n1.get("city").asString(null)) + .categoryName(n1.get("category_name").asString(null)) + .build(); + + GroupNeo4J g2 = GroupNeo4J.builder() + .groupId(n2.get("group_id").asString()) + .groupName(n2.get("group_name").asString(null)) + .description(n2.get("description").asString(null)) + .cityName(n2.get("city").asString(null)) + .categoryName(n2.get("category_name").asString(null)) + .build(); + + return GroupCommunityDTO.builder() + .group1(g1) + .group2(g2) + .sharedMembers(record.get("sharedMembers").asLong()) + .build(); + }) + .all().stream().toList(); + } +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/GroupNeo4JRepository.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/GroupNeo4JRepository.java new file mode 100644 index 000000000..997964fa4 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/GroupNeo4JRepository.java @@ -0,0 +1,56 @@ +package com.example.joinUs.repository; + +import com.example.joinUs.model.neo4j.GroupNeo4J; +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.neo4j.repository.query.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + + +@Repository +public interface GroupNeo4JRepository extends Neo4jRepository { + + @Query(""" + MATCH (u:Group { group_id: $groupId }) + DETACH DELETE u + """) + void deleteGroup(String groupId); + + @Query(""" + MATCH (m:Member {member_id: $memberId})-[:MEMBER_OF]->(g:Group) + RETURN DISTINCT g + """) + List findAllGroupsOfUser(String memberId); + + @Query(""" + MATCH (g:Group {group_id: $groupId})<-[:MEMBER_OF]-(m:Member), + (g)-[:ORGANIZES]->(e:Event)<-[:ATTENDS]-(m) + RETURN COUNT(DISTINCT m) + """) + long countActiveMembersInGroup(String groupId); + + @Query(""" + MATCH (m:Member {member_id: $memberId})-[:INTERESTED_IN]->(t:Topic)<-[:HAS_TOPIC]-(g:Group) + WHERE NOT (m)-[:MEMBER_OF]->(g) + WITH g AS group, COUNT(DISTINCT t) AS score + ORDER BY score DESC + LIMIT 10 + RETURN group + """) + List recommendGroupsByTopics(String memberId, @Param("limit") long limit); + //Recommend groups to a member based on the topics they are interested in + + @Query(""" + MATCH (me:Member {member_id: $memberId})-[:MEMBER_OF]->(g1:Group)<-[:MEMBER_OF]-(other:Member)-[:MEMBER_OF]->(g2:Group) + WHERE NOT (me)-[:MEMBER_OF]->(g2) + WITH g2 AS group, COUNT(DISTINCT other) AS score + ORDER BY score DESC + LIMIT $limit + RETURN group + """) + List recommendGroupsBySimilarMembers(String memberId, @Param("limit") long limit); + //Recommend groups for a member based on their current groups and other groups of the other members + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/GroupRepository.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/GroupRepository.java new file mode 100644 index 000000000..7eda8394f --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/GroupRepository.java @@ -0,0 +1,93 @@ +package com.example.joinUs.repository; + +import com.example.joinUs.dto.analytics.GroupsPerCityAnalytic; +import com.example.joinUs.dto.analytics.GroupsPerOrganizerAnalytic; +import com.example.joinUs.dto.analytics.GroupsPerTopicAnalytic; +import com.example.joinUs.model.mongodb.Group; +import org.bson.Document; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.mongodb.repository.Aggregation; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + + +@Repository +public interface GroupRepository extends MongoRepository { + + String fields = "{'_id':1,'group_name':1,'upcoming_events':1,'city.name':1','category.name':1,'member_count':1,'event_count':1}"; + String projection = "{$project:" + fields + "}"; + + Optional findById(String id); + + Page findAll(Pageable pageable); + + boolean existsById(String id); + + void deleteById(String id); + + @Query("{'organizers.member_id' : ?0 }") + List findGroupsByOrganizerId(String id); + + //{$or:[{group_id:"5817263"},{group_name:"San Francisco Startup Socials"}]} + + @Query("{$or:[{ '_id': ?0 },{ 'group_name': ?1 }]}") + List findGroupByGroupIdOrGroupName(String groupId, String groupName); + + // Aggregations for Admin page + //Top Cities by Groups Created in the Past 10 Years + + // Count Groups per Category + @Aggregation(pipeline = { + "{ $unwind: 'topics' }", + "{ $group: { _id: '$topics.topic_name', groupsCount: { $sum: 1 } } }", + "{ $sort: { groupsCount: -1 } }", + "{ $limit: ?0 }", + "{$project: {topic:'$_id',groupsCount:1,_id:0}}" + }) + List countGroupsByTopic(int limit); + + //Top Cities by Groups Created in the Past 10 Years + @Aggregation(pipeline = { + "{ $match: { created: { $gte: { $dateSubtract: { startDate: '$$NOW', unit: 'year', amount: 10 } } } } }", + "{ $group: { _id: '$city.name', groupsCreated: { $sum: 1 } } }", + "{ $sort: { groupsCreated: -1 } }", + "{ $limit: ?0 }", + "{ $project: { city: '$_id', groupsCreated: 1, _id: 0 } }" + }) + List topCitiesByGroupsLast10Years(int limit); + + @Aggregation(pipeline = { + "{ $match: { created: { $gte: { $dateSubtract: { startDate: '$$NOW', unit: 'month', amount: 1 } } } } }", + "{ $unwind: '$attendees' }", + "{ $group: { _id: '$category_name', attendance: { $sum: 1 } } }", + "{ $sort: { attendance: -1 } }", + "{ $limit: 10 }", + "{ $project: { category: '$_id', attendance: 1, _id: 0 } }" + }) + List trendingCategoriesLastMonth(); + + //Groups created per year (trend) showing Growth of groups over time. + @Aggregation(pipeline = { + "{ $match: { created: { $ne: null } } }", + "{ $group: { _id: { $year: '$created' }, groupsCreated: { $sum: 1 } } }", + "{ $sort: { _id: 1 } }", + "{ $project: { year: '$_id', groupsCreated: 1, _id: 0 } }" + }) + List groupsCreatedPerYear(); + + //Organizer leaderboard (top organizers by number of groups) + @Aggregation(pipeline = { + "{ $unwind: '$organizer_members' }", + "{ $group: { _id: '$organizer_members.member_id', organizerName: { $first: '$organizer_members.member_name' }" + + ", groupsOrganized: { $sum: 1 } } }", + "{ $sort: { groupsOrganized: -1 } }", + "{ $limit: ?0 }", + "{ $project: { userId: '$_id', organizerName: 1, groupsOrganized: 1, _id: 0 } }" + }) + List topOrganizersByGroups(int limit); +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/TopicNeo4JRepository.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/TopicNeo4JRepository.java new file mode 100644 index 000000000..112beb66d --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/TopicNeo4JRepository.java @@ -0,0 +1,43 @@ +package com.example.joinUs.repository; + +import com.example.joinUs.model.neo4j.TopicNeo4J; +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.neo4j.repository.query.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + + +@Repository +public interface TopicNeo4JRepository extends Neo4jRepository { + + // List findByTopicName(String topicName); + + @Query(""" + MATCH (u:Topic { topic_id: $topicId }) + DETACH DELETE u + """) + void deleteTopic(String topicId); + + @Query(""" + MATCH (m:Member)-[:INTERESTED_IN]->(t)-[:HAS_TOPIC]->(g:Group) + WITH t, count(DISTINCT m) AS memberCount, count(DISTINCT g) AS groupCount + ORDER BY (memberCount+groupCount) + RETURN t + LIMIT $limit + """) + List findMostPopularTopicsAmongGroupsAndUsers(@Param("limit") long limit); + //Find topics of groups, which users are most interested in + + @Query(""" + MATCH (m:Member)-[:INTERESTED_IN]->(t:Topic)-[:HAS_TOPIC]->(g:Group {group_id: $groupId }) + WITH t, count(DISTINCT m) AS memberCount, count(DISTINCT g) AS groupCount + ORDER BY (memberCount+groupCount) + RETURN t + LIMIT $limit + """) + List findGroupsTopicsWhichUsersAreMostInterestedIn(@Param("limit") long limit, + @Param("groupId") String groupId); + +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/TopicRepository.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/TopicRepository.java new file mode 100644 index 000000000..0ccda28bf --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/TopicRepository.java @@ -0,0 +1,9 @@ +package com.example.joinUs.repository; + +import com.example.joinUs.model.mongodb.Topic; +import org.springframework.data.mongodb.repository.MongoRepository; + + +public interface TopicRepository extends MongoRepository { + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/UserNeo4JRepository.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/UserNeo4JRepository.java new file mode 100644 index 000000000..141ba0c31 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/UserNeo4JRepository.java @@ -0,0 +1,45 @@ +package com.example.joinUs.repository; + +import com.example.joinUs.model.neo4j.UserNeo4J; +import org.springframework.data.neo4j.repository.Neo4jRepository; +import org.springframework.data.neo4j.repository.query.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + + +@Repository +public interface UserNeo4JRepository extends Neo4jRepository { + + @Query( + """ + MATCH (p:Member)-[:MEMBER_OF]-(n:Group { group_id: $groupId }) + RETURN DISTINCT n ; + """ + ) + List getMembersLinkedToGroup(String groupId); + + @Query(""" + MATCH (u:Member { member_id: $memberId }) + DETACH DELETE u + """) + void deleteUser(String memberId); + + @Query(""" + MATCH (m:Member {member_id: $userId}) + MATCH (g:Group {group_id: $groupId}) + MERGE (m)-[:MEMBER_OF]->(g) + MERGE (g)-[:MEMBER_OF]->(m) + """) + void addUserToGroup(String userId, String groupId); + + @Query(""" + MATCH (m:Member {member_id: $userId}) + MATCH (g:Group {group_id: $groupId}) + MATCH (m)-[r1:MEMBER_OF]->(g) + MATCH (g)-[r2:MEMBER_OF]->(m) + DELETE r1, r2 + """) + void removeUserFromGroup(String userId, String groupId); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/UserRepository.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/UserRepository.java new file mode 100644 index 000000000..3e65bb9ac --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/repository/UserRepository.java @@ -0,0 +1,111 @@ +package com.example.joinUs.repository; + +import com.example.joinUs.dto.analytics.*; +import com.example.joinUs.model.mongodb.User; +import org.springframework.data.mongodb.repository.Aggregation; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + + +@Repository +public interface UserRepository extends MongoRepository { + + // Find by exact member name + @Query("{ '_id': ?0 }") + List findMemberById(String member_id); + + // Find by exact member name + @Query("{ 'member_name': ?0 }") + List findMemberByName(String member_name); + // Find by member_id (you could also just use findById inherited from MongoRepository) + // Optional findByMember_id(String member_id); + + // Find all users in a specific city + @Query("{ 'city.name': ?0 }") + List findByCity(String cityName); + + // Find all users who are admins + @Query("{ 'isAdmin': true }") + List findAdmins(); + + // Find users who have organized a specific group + @Query("{ 'created_groups': ?0 }") + List findByCreated_groupsContaining(String groupId); + + // Find users by topic key + @Query("{ 'topics.topic_name': ?0 }") + List findByTopicName(String topic_name); + + // Update operations via repository usually use save(entity) + // Example usage: + // 1. Retrieve user: Optional u = userRepository.findById(id); + // 2. Modify fields on User object + // 3. Save: userRepository.save(user); + + // Delete by member_id + // void deleteByMember_id(String member_id); + + // Count users in a specific city + @Query(value = "{ 'city.name': ?0 }", count = true) + long countByCity(String cityName); + + @Aggregation(pipeline = { + "{\n" + + " $project: {\n" + + " userId: '$_id',\n" + + " userName:'$member_name',\n" + + " activityScore: {\n" + + " $add: [\n" + + " { $ifNull: [\"$event_count\", 0] },\n" + + " { $ifNull: [\"$group_count\", 0] }\n" + + " ]\n" + + " },\n" + + " _id:0\n" + + " }\n" + + " }", + "{ $sort: { activityScore: -1 } }", + "{ $limit: ?0 }" + }) + List topUsersByActivityScore(int limit); + + //Topic Popularity Among Users + @Aggregation(pipeline = { + "{ $unwind: '$topics' }", + "{ $group: { _id: '$topics.topic_name', usersCount: { $sum: 1 } } }", + "{ $sort: { usersCount: -1 } }", + "{ $limit: ?0 }", + " {$project: {topic:'$_id',usersCount:1,_id:0}}" + }) + List mostPopularUserTopics(int limit); + + //Topic popularity per city (top topics inside each city) showing shows for each city, which topics appear most. + @Aggregation(pipeline = { + "{ $unwind: '$topics' }", + "{ $group: { _id: { city: '$city.name', topic: '$topics.topic_name' }, usersCount: { $sum: 1 } } }", + "{ $sort: { usersCount: -1 } }", + "{ $group: { _id: '$_id.city', topTopics: { $push: { topic: '$_id.topic', usersCount: '$usersCount' } } } }", + "{ $project: { city: '$_id', topTopics: { $slice: ['$topTopics', ?0] }, _id: 0 } }", + }) + List topTrendingTopicsPerCity(int topicCount); + + @Aggregation(pipeline = { + "{ $project: { city: '$city.name', isActive: { $gt: [ { $size: { $ifNull: ['$upcoming_events', []] } }, 0 ] } } }", + "{ $group: { _id: '$city', totalMembers: { $sum: 1 }, activeMembers: { $sum: { $cond: ['$isActive', 1, 0] } } } }", + "{ $project: { city: '$_id', totalMembers: 1, activeMembers: 1, activityRatio: { $divide: ['$activeMembers', '$totalMembers'] }, _id: 0 } }", + "{ $sort: { activityRatio: -1 } }", + "{ $limit: 10 }" + }) + List mostActiveCities(); + + @Aggregation(pipeline = { + "{ $group: { _id: '$city.name', usersCount: { $sum: 1 } } }", + "{ $sort: { usersCount: -1 } }", + "{ $limit: ?0 }", + "{$project:{city:'$_id',usersCount:1,_id:0}" + }) + List countUsersByCity(int limit); + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/AdminService.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/AdminService.java new file mode 100644 index 000000000..c57294a5d --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/AdminService.java @@ -0,0 +1,79 @@ +package com.example.joinUs.service; + +import com.example.joinUs.Utils; +import com.example.joinUs.dto.GroupCommunityDTO; +import com.example.joinUs.dto.analytics.*; +import com.example.joinUs.mapping.EventMapper; +import com.example.joinUs.mapping.GroupMapper; +import com.example.joinUs.mapping.TopicMapper; +import com.example.joinUs.model.mongodb.User; +import com.example.joinUs.repository.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + + +@Service +public class AdminService { + + @Autowired + private EventNeo4JRepository eventNeo4JRepository; + @Autowired + private GroupNeo4JRepository groupNeo4JRepository; + @Autowired + private UserNeo4JRepository userNeo4JRepository; + @Autowired + private TopicNeo4JRepository topicNeo4JRepository; + @Autowired + private GroupCommunityRepository groupCommunityRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private EventRepository eventRepository; + + @Autowired + private GroupRepository groupRepository; + + @Autowired + private TopicRepository topicRepository; + + @Autowired + TopicMapper topicMapper; + + @Autowired + EventMapper eventMapper; + + @Autowired + GroupMapper groupMapper; + + public List topUsersByActivityScore(int limit) { + return userRepository.topUsersByActivityScore(limit); + } + + public List mostActiveCities(int limit) { + return userRepository.mostActiveCities(); + } + + public List topCitiesByGroupsLast10Years(int limit) { + return groupRepository.topCitiesByGroupsLast10Years(limit); + } + + public List topTrendingTopicsPerCity(int topicCount) { + return userRepository.topTrendingTopicsPerCity(topicCount); + } + + public List paidVsFreePopularity() { + return eventRepository.paidVsFreePopularity(); + } + + public long countActiveMembersInGroup(String groupId) { + return groupNeo4JRepository.countActiveMembersInGroup(groupId); + } + + public List findGroupCommunities(int sharedMembers, int limit) { + return groupCommunityRepository.findGroupCommunities(sharedMembers, limit); + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/CityService.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/CityService.java new file mode 100644 index 000000000..53e22d437 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/CityService.java @@ -0,0 +1,39 @@ +package com.example.joinUs.service; + +import com.example.joinUs.Utils; +import com.example.joinUs.mapping.CityMapper; +import com.example.joinUs.model.embedded.CityEmbedded; +import com.example.joinUs.model.mongodb.City; +import com.example.joinUs.repository.CityRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.stereotype.Service; + + +@Service +public class CityService { + + @Autowired + private CityRepository cityRepository; + @Autowired + private CityMapper cityMapper; + + @Autowired + private MongoTemplate mongoTemplate; + + public void parseCity(CityEmbedded cityOld) { + if (!Utils.isNullOrEmpty(cityOld)) { + if (!Utils.isNullOrEmpty(cityOld.getCityId())) { + String cityId = cityOld.getCityId(); + City city = cityRepository.findByCityId(cityId); + if (city != null) cityOld = cityMapper.toEmbedded(city); + } else if (!Utils.isNullOrEmpty(cityOld.getName())) { + String cityName = cityOld.getName(); + City city = cityRepository.findByName(cityName); + if (city != null) cityOld = cityMapper.toEmbedded(city); + } + } + } + +} + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/CustomUserDetailsService.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/CustomUserDetailsService.java new file mode 100644 index 000000000..d8377e167 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/CustomUserDetailsService.java @@ -0,0 +1,45 @@ +package com.example.joinUs.service; + +import com.example.joinUs.Utils; +import com.example.joinUs.model.mongodb.User; +import com.example.joinUs.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.List; + + +@Service +public class CustomUserDetailsService implements UserDetailsService { + + @Autowired + private final UserRepository userRepository; + + @Autowired + private final MongoTemplate mongoTemplate; + + public CustomUserDetailsService(UserRepository userRepository, MongoTemplate mongoTemplate) { + this.userRepository = userRepository; + this.mongoTemplate = mongoTemplate; + } + + @Override + public UserDetails loadUserByUsername(String username) + throws UsernameNotFoundException { + + List users = userRepository.findMemberById(username); + //TODO need to change this later in order to guarantee uniqueness by username , (for now best I can think of is just using member_id) + + if (Utils.isNullOrEmpty(users)) + throw new UsernameNotFoundException("No User is found with username " + username); + + User user = users.get(0); + + return user; + } +} + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/EventService.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/EventService.java new file mode 100644 index 000000000..6af164476 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/EventService.java @@ -0,0 +1,439 @@ +package com.example.joinUs.service; + +import com.example.joinUs.Utils; +import com.example.joinUs.dto.EventDTO; +import com.example.joinUs.dto.ResponseMessage; +import com.example.joinUs.dto.summary.EventSummaryDTO; +import com.example.joinUs.mapping.EventMapper; +import com.example.joinUs.mapping.embedded.EventEmbeddedMapper; +import com.example.joinUs.mapping.embedded.GroupEmbeddedMapper; +import com.example.joinUs.mapping.summary.EventSummaryMapper; +import com.example.joinUs.model.embedded.EventEmbedded; +import com.example.joinUs.model.embedded.GroupEmbedded; +import com.example.joinUs.model.mongodb.*; +import com.example.joinUs.model.neo4j.EventNeo4J; +import com.example.joinUs.repository.EventNeo4JRepository; +import com.example.joinUs.repository.EventRepository; +import com.example.joinUs.repository.GroupRepository; +import com.example.joinUs.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import java.util.Date; +import java.util.List; + +import static org.springframework.http.HttpStatus.NOT_FOUND; + + +@Service +public class EventService { + + @Autowired + private EventRepository eventRepository; + @Autowired + private UserRepository userRepository; + + @Autowired + private GroupRepository groupRepository; + + @Autowired + private CityService cityService; + + @Autowired + private EventMapper eventMapper; + + @Autowired + private EventEmbeddedMapper eventEmbeddedMapper; + @Autowired + private GroupEmbeddedMapper groupEmbeddedMapper; + + @Autowired + private EventSummaryMapper eventSummaryMapper; + + @Autowired + private UserService userService; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private EventNeo4JRepository eventNeo4JRepository; + + // @Autowired + // private Neo4jClient neo4jClient; + + @Autowired + private EventNeo4JRepository eventNeo4JRepo; + + public List getEventsFromNeo4j() { + return eventNeo4JRepo.findAll(); + } + + public Page getAllEvents(int page, int size) { + // Page events = eventRepository.findAllEvents(PageRequest.of(page, size)); + Page events = eventRepository.findAll(PageRequest.of(page, size)); + return events.map(e -> eventSummaryMapper.toDTO(e)); + } + + public EventDTO getEventById(String id) { + + // return eventRepository.findEventWithId(id); + + Event event = getEventOrThrow(id); + EventDTO eventDTO = eventMapper.toDTO(event); + // + return eventDTO; + } + + public ResponseMessage attendEvent(String id) { + Event event = getEventOrThrow(id); + try { + User user = Utils.getUserFromContext(); + List userUpcomingEvents = user.getUpcomingEvents(); + userUpcomingEvents.add(eventEmbeddedMapper.toDTO(event)); + user.setUpcomingEvents(userUpcomingEvents); + user.setEventCount(user.getEventCount() + 1); + mongoTemplate.updateFirst( + Query.query(Criteria.where("_id").is(id)), + new Update().inc("member_count", 1), + Event.class + );// This is atomic operation per document ,so it is thread safe if 2 users attend the same event simultaneously + + userRepository.save(user); + eventNeo4JRepo.addUserAttending(id, user.getId()); + + return new ResponseMessage("successful", + "Your attendance in the event " + event.getEventName() + " is confirmed"); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + } + + public ResponseMessage revokeAttendance(String id) { + + try { + User user = Utils.getUserFromContext(); + user.removeUpcomingEvent(id); + user.setEventCount(user.getEventCount() - 1); + userRepository.save(user); + + mongoTemplate.updateFirst( + Query.query(Criteria.where("_id").is(id)), + new Update().inc("member_count", -1), + Event.class + ); // This is atomic operation per document ,so it is thread safe if 2 users attend the same event simultaneously + + eventNeo4JRepo.revokeUserAttending(id, user.getId()); + //TODO complete the part for the Neo4J too + return new ResponseMessage("successful", "Your attendance in the event is revoked "); + + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + } + + public EventDTO createEvent(EventDTO eventDTO) { + + EventDTO eventDTO1 = createEventDTO(eventDTO); + try { + Event entity = eventMapper.toEntity(eventDTO1); + Event saved = eventRepository.save(entity); + eventDTO1.setId(saved.getId()); + eventNeo4JRepo.save(eventMapper.toNeo4jEntity(eventDTO1)); + return eventMapper.toDTO(saved); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + + } + + private EventDTO createEventDTO(EventDTO eventDTO) { + + if (Utils.isNullOrEmpty(eventDTO.getId())) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "eventId must be provided"); + } + if (eventRepository.existsById(eventDTO.getId())) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "Event already exists: " + eventDTO.getId()); + } + + if (Utils.isNullOrEmpty(eventDTO.getEventName())) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "eventName must be provided"); + } + if (Utils.isNullOrEmpty(eventDTO.getEventTime())) { + throw new ResponseStatusException(HttpStatus.CONFLICT, "eventTime must be provided"); + } + + eventDTO.setCreated(new Date()); + eventDTO.setUpdated(new Date()); + eventDTO.setMemberCount(0); + + if (Utils.isNullOrEmpty(eventDTO.getFee())) eventDTO.setFee(Fee.getDefault()); + + Venue venue = updateVenueForEvent(eventDTO); + if (venue != null) eventDTO.setVenue(venue); + + setCreatorGroupForCreate(eventDTO); + + return eventDTO; + + } + + public EventDTO updateEvent(EventDTO eventDTO, String id) { + + userService.checkUserHasPermissionToEditEvent(id); + + Event event = getEventOrThrow(id); + + try { + + // eventDTO.setCreatorGroup(groupEmbeddedMapper.toDTO(event.getCreatorGroup())); + eventDTO.setCreatorGroup(event.getCreatorGroup()); + if (Utils.isNullOrEmpty(eventDTO.getEventName())) eventDTO.setEventName(event.getEventName()); + if (Utils.isNullOrEmpty(eventDTO.getEventTime())) eventDTO.setEventTime(event.getEventTime()); + if (Utils.isNullOrEmpty(eventDTO.getDescription())) eventDTO.setDescription(event.getDescription()); + if (Utils.isNullOrEmpty(eventDTO.getFee())) { + eventDTO.setFee(event.getFee()); + } + if (Utils.isNullOrEmpty(eventDTO.getDuration())) eventDTO.setDuration(event.getDuration()); + if (Utils.isNullOrEmpty(eventDTO.getCategory())) eventDTO.setCategory(event.getCategory()); + + if (Utils.isNullOrEmpty(eventDTO.getVenue())) { + eventDTO.setVenue(event.getVenue()); + } else cityService.parseCity(event.getVenue().getCity()); + + //Fields that should remain the same + eventDTO.setUpdated(new Date()); + eventDTO.setCreated(event.getCreated()); + eventDTO.setMemberCount(event.getMemberCount()); + + // eventDTO.getCreatorGroup().setId(event.getCreatorGroup().getId()); + // eventDTO.getCreatorGroup().setGroupName(event.getEventName()); + eventDTO.setId(event.getId()); + eventRepository.save(eventMapper.toEntity(eventDTO)); + eventNeo4JRepo.save(eventMapper.toNeo4jEntity(eventDTO));//TODO this can be parallelized + return eventDTO; + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + + } + + private Event getEventOrThrow(String id) { + return eventRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Event not found: " + id)); + } + + public void deleteEvent(String id) { + Event event = getEventOrThrow(id); + userService.checkUserHasPermissionToEditEvent(id); + + try { + User user = Utils.getUserFromContext(); + + user.removeUpcomingEvent(id);//TODO also complete the edge removal for Neo4J + + GroupEmbedded group = event.getCreatorGroup(); + + eventRepository.deleteById(id); + eventNeo4JRepo.deleteEvent(id); + + userRepository.save(user); + + mongoTemplate.updateFirst(Query.query(Criteria.where("_id").is(group.getGroupId())), + new Update().pull("upcoming_events", + Query.query(Criteria.where("event_id").is(event.getId()))) + .inc("event_count", -1) + , Group.class); + + } catch (Exception exception) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, exception.getMessage()); + } + + } + + public Page search( + String name, + String city, + String category, + Integer minMembers, + Integer maxMembers, + Date fromDate, + Date toDate, + Integer maxFee, + int page, + int pageSize + ) { + Pageable pageable = PageRequest.of(page, pageSize); + Query query = new Query(); + + if (name != null) + query.addCriteria(Criteria.where("event_name").regex(name, "i")); + + if (city != null) + query.addCriteria(Criteria.where("venue.city.name").regex(city, "i")); + + if (minMembers != null || maxMembers != null) { + Criteria memberCountCriteria = Criteria.where("member_count"); + + if (minMembers != null) + memberCountCriteria.gte(minMembers); + + if (maxMembers != null) + memberCountCriteria.lte(maxMembers); + + query.addCriteria(memberCountCriteria); + } + + if (fromDate != null || toDate != null) { + Criteria dateCriteria = Criteria.where("event_time"); + + if (fromDate != null) + dateCriteria.gte(fromDate); + + if (toDate != null) + dateCriteria.lte(toDate); + + query.addCriteria(dateCriteria); + } + + if (category != null) + query.addCriteria(Criteria.where("category.name").is(category)); + + if (maxFee != null) + query.addCriteria(Criteria.where("fee.amount").lte(maxFee)); + query.with(pageable); + + List results = mongoTemplate.find(query, Event.class) + .stream() + .map(e -> eventSummaryMapper.toDTO(e)) + .toList(); + + return new PageImpl(results, pageable, -1); + // return results; + + } + + private Venue updateVenueForEvent(EventDTO eventDTO) { + Venue venue = eventDTO.getVenue(); + if (venue != null) { + cityService.parseCity(venue.getCity()); + } + return venue; + } + + private void setVenueAndCityForUpdate(Event newEvent, EventDTO eventDTO) { + Venue venue = updateVenueForEvent(eventDTO); + if (venue != null) newEvent.setVenue(venue); + } + + public void setCreatorGroupForCreate(EventDTO eventDTO) { + + if (Utils.isNullOrEmpty(eventDTO.getCreatorGroup())) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "Please provide a groupId or a groupName that will represent the creatorGroup of the event"); + } + String groupName = eventDTO.getCreatorGroup().getGroupName(); + String groupId = eventDTO.getCreatorGroup().getGroupId(); + + List groups = groupRepository.findGroupByGroupIdOrGroupName(groupId, groupName); + if (Utils.isNullOrEmpty(groups)) { + throw new ResponseStatusException(NOT_FOUND, + "No group exists with groupId " + groupId + " or groupName " + groupName); + } + + Group group = groups.get(0); + + mongoTemplate.updateFirst(Query.query(Criteria.where("_id").is(group.getId())), + new Update().push("organizer_members", + eventEmbeddedMapper.toDTO(eventMapper.toEntity(eventDTO))) + .inc("event_count", 1), + Group.class); //To ensure atomicity if from the same groups are being created events simultaneously + + userService.checkUserHasPermissionToEditGroup(group.getId()); + + eventDTO.setCreatorGroup(groupEmbeddedMapper.toDTO(group)); + + eventNeo4JRepo.addGroupEventRelation(eventDTO.getCreatorGroup().getGroupId(), eventDTO.getId()); + //Neo4j operation + } + + public List findEventsByCreatorGroup(String creatorGroupId) { + return eventRepository.findEventsByCreatorGroup(creatorGroupId); + } + + public List findEventsOfUser(String userId) { + return eventNeo4JRepo.findAllEventsAttendedByUser(userId).stream() + .map(e -> eventMapper.toDTOFromNeo4j(e)).toList(); + } + + public List findEventsOrganizedByGroup(String groupId) { + return eventNeo4JRepo.findAllEventsOrganizedByGroup(groupId) + .stream().map(e -> eventMapper.toDTOFromNeo4j(e)).toList(); + } + + // public Page filterByCategory(String category, int page, int size) { + // Pageable pageable = PageRequest.of(page, size + 1); + // return eventRepository.findByCategoryName(category, pageable); + // } + // + // + // public Page filterByMemberCount( + // int min, + // int max, + // int page, int size + // ) { + // long startTime = System.currentTimeMillis(); + // + // Pageable pageable = + // PageRequest.of(page, size + 1); + // + // Page page1 = eventRepository.findByMemberCountBetween(min, max, pageable); + // long endTime = System.currentTimeMillis(); + // System.out.println("PROCESS USING QUERY LASTED : " + (endTime - startTime) + " milliseconds"); + // return page1; + // } + // + // public Page filterByDateRange(LocalDateTime from, LocalDateTime to, int page, int size) { + // + // try { + // LocalDateTime date = LocalDateTime.now(); + // + // if (to != null) + // return eventRepository.findByEventTimeBetween(from != null ? from : date, to, PageRequest.of(page, size + 1)); + // else { + // return eventRepository.findByEventTimeAfter(from != null ? from : date, PageRequest.of(page, size + 1)); + // } + // } catch (Exception e) { + // throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); + // } + // + // + // } + // + // public Page filterByMaxFee(int maxFee, int page, int size) { + // return eventRepository.findByEventFeeIsLessOrEqual(maxFee, PageRequest.of(page, size + 1)); + // } + // + // public Page filterByCity(String city, int page, int size) { + // return eventRepository.findByCityName(city, PageRequest.of(page, size + 1)); + // } + // + // public Page findByEventNameContainingIgnoreCase(String name, int page, int size) { + // Pageable pageable = PageRequest.of(page, size + 1); + // + // return eventRepository.searchByEventName(name, pageable); + // } + +} + + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/GroupService.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/GroupService.java new file mode 100644 index 000000000..a0ff3019e --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/GroupService.java @@ -0,0 +1,291 @@ +package com.example.joinUs.service; + +import com.example.joinUs.Utils; +import com.example.joinUs.dto.GroupDTO; +import com.example.joinUs.dto.ResponseMessage; +import com.example.joinUs.dto.UserDTO; +import com.example.joinUs.dto.summary.EventSummaryDTO; +import com.example.joinUs.dto.summary.GroupSummaryDTO; +import com.example.joinUs.mapping.GroupMapper; +import com.example.joinUs.mapping.GroupPhotoMapper; +import com.example.joinUs.mapping.embedded.UserEmbeddedMapper; +import com.example.joinUs.mapping.summary.GroupSummaryMapper; +import com.example.joinUs.model.mongodb.Group; +import com.example.joinUs.model.mongodb.User; +import com.example.joinUs.repository.GroupNeo4JRepository; +import com.example.joinUs.repository.GroupRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import static org.springframework.http.HttpStatus.NOT_FOUND; + + +@Service +public class GroupService { + + @Autowired + private GroupRepository groupRepository; + + @Autowired + GroupNeo4JRepository groupNeo4JRepository; + + @Autowired + private UserService userService; + + @Autowired + private CityService cityService; + + @Autowired + private GroupMapper groupMapper; + + @Autowired + private EventService eventService; + + @Autowired + private GroupSummaryMapper groupSummaryMapper; + + @Autowired + private UserEmbeddedMapper userEmbeddedMapper; + + @Autowired + private MongoTemplate mongoTemplate; + + @Autowired + private GroupPhotoMapper groupPhotoMapper; + + public ResponseMessage joinGroup(String id) { + Group group = getGroupOrThrow(id); + try { + + User user = Utils.getUserFromContext(); //TODO maybe throw error if user is already part of group + + user.setGroupCount(user.getGroupCount() + 1); + + mongoTemplate.updateFirst( + Query.query(Criteria.where("_id").is(id)), + new Update().inc("member_count", 1), + Group.class + );// This is atomic operation per document ,so it is thread safe if 2 users join the same group simultaneously + + userService.saveUser(user); + userService.addUserToGroup(user.getId(), id); //Neo4J operation + + return new ResponseMessage("successful", + "Your attendance in the group " + group.getGroupName() + " is confirmed"); + + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + } + + public ResponseMessage leaveGroup(String id) { + Group group = getGroupOrThrow(id); + try { + + User user = Utils.getUserFromContext();//TODO maybe throw error if user is not part of group + user.setGroupCount(user.getGroupCount() - 1); + mongoTemplate.updateFirst( + Query.query(Criteria.where("_id").is(id)), + new Update().inc("member_count", -1), + Group.class + );// This is atomic operation per document ,so it is thread safe if 2 users join the same group simultaneously + + userService.saveUser(user); + userService.removeUserFromGroup(user.getId(), id); //Neo4J operation + return new ResponseMessage("successful", "You left the group " + group.getGroupName() + " "); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + } + + public ResponseMessage addOrganizer(String groupId, String organizerId) { + Group group = getGroupOrThrow(groupId); + + if (group.getOrganizers() + .stream() + .anyMatch(u -> u.getMemberId().equals(organizerId))) { + throw new ResponseStatusException( + HttpStatus.CONFLICT, "User is already an organizer at this group " + groupId); + } + + userService.checkUserHasPermissionToEditGroup(groupId); + User user = userService.findUserById(organizerId).orElseThrow( + () -> new ResponseStatusException(NOT_FOUND, "No user exists with id " + organizerId) + ); + group.getOrganizers().add(userEmbeddedMapper.toDTO(user)); + groupRepository.save(group); + userService.addUserToGroup(organizerId, groupId); //Neo4J Safe because the query is MERGE not create + return new ResponseMessage("successful", "Organizer with id" + organizerId + " was added successfully"); + } + + public Page getAllGroups(int page, int size) { + Page groups = groupRepository.findAll(PageRequest.of(page, size)); + return groups.map(e -> groupSummaryMapper.toDTO(e)); + } + + public GroupDTO getGroupById(String id) { + Group group = getGroupOrThrow(id); + return groupMapper.toDTO(group); + } + + public PageImpl search( + String name, + String city, + String category, + Integer minMembers, + Integer maxMembers, + Integer minEvents, + Integer maxEvents, + int page, + int pageSize + ) { + Pageable pageable = PageRequest.of(page, pageSize); + Query query = new Query(); + + if (name != null) + query.addCriteria(Criteria.where("group_name").regex(name, "i")); + + if (city != null) + query.addCriteria(Criteria.where("city.name").regex(city, "i")); + + if (minMembers != null || maxMembers != null) { + Criteria memberCountCriteria = Criteria.where("member_count"); + + if (minMembers != null) + memberCountCriteria.gte(minMembers); + + if (maxMembers != null) + memberCountCriteria.lte(maxMembers); + + query.addCriteria(memberCountCriteria); + } + + if (minEvents != null || maxEvents != null) { + Criteria eventCountCriteria = Criteria.where("event_count"); + + if (minEvents != null) + eventCountCriteria.gte(minEvents); + + if (maxEvents != null) + eventCountCriteria.lte(maxEvents); + + query.addCriteria(eventCountCriteria); + } + + if (category != null) + query.addCriteria(Criteria.where("category.name").is(category)); + + query.with(pageable); + + List results = mongoTemplate.find(query, Group.class) + .stream() + .map(e -> groupSummaryMapper.toDTO(e)) + .toList(); + + return new PageImpl(results, pageable, -1); + // return results; + + } + + public GroupDTO createGroup(GroupDTO groupDTO) { + + User user = Utils.getUserFromContext(); + + // Minimal approach: require groupId to be present and unique + // if (Utils.isNullOrEmpty(groupDTO.getId())) { + // throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "id must be provided"); + // } + // if (groupRepository.existsById(groupDTO.getId())) { + // throw new ResponseStatusException(HttpStatus.CONFLICT, "Group already exists: " + groupDTO.getId()); + // } + groupDTO.setCreated(new Date()); + groupDTO.setUpcomingEvents(new ArrayList<>()); + groupDTO.setMemberCount(1); + groupDTO.setEventCount(0); + groupDTO.getOrganizers().add(userEmbeddedMapper.toDTO(user)); + cityService.parseCity(groupDTO.getCity()); + Group entity = groupMapper.toEntity(groupDTO); + Group saved = groupRepository.save(entity); + groupDTO.setId(saved.getId()); + groupNeo4JRepository.save(groupMapper.toNeo4JEntity(groupDTO)); + + return groupMapper.toDTO(saved); + } + + public GroupDTO updateGroup(String id, GroupDTO groupDTO) { // TODO revisit, especially PATCH semantics + + Group existing = getGroupOrThrow(id); + + userService.checkUserHasPermissionToEditGroup(id); + // Minimal patch semantics: + // - map incoming DTO to entity and copy non-null fields onto existing + if (Utils.isNullOrEmpty(groupDTO.getGroupName())) groupDTO.setGroupName(existing.getGroupName()); + if (Utils.isNullOrEmpty(groupDTO.getGroupPhoto())) + groupDTO.setGroupPhoto(groupPhotoMapper.toDTO(existing.getGroupPhoto())); + if (Utils.isNullOrEmpty(groupDTO.getDescription())) groupDTO.setDescription(existing.getDescription()); + if (Utils.isNullOrEmpty(groupDTO.getCategory())) groupDTO.setCategory(existing.getCategory()); + if (Utils.isNullOrEmpty(groupDTO.getTopics())) groupDTO.setTopics(existing.getTopics()); + + if (Utils.isNullOrEmpty(groupDTO.getCity())) { + groupDTO.setCity(existing.getCity()); + } else cityService.parseCity(groupDTO.getCity()); + + // Ensure the identifier stays consistent ,and other fields + groupDTO.setId(existing.getId()); + groupDTO.setEventCount(existing.getEventCount()); + groupDTO.setMemberCount(existing.getMemberCount()); + groupDTO.setCreated(existing.getCreated()); + groupDTO.setUpcomingEvents(existing.getUpcomingEvents()); + groupDTO.setOrganizers(existing.getOrganizers()); + + Group saved = groupRepository.save(groupMapper.toEntity(groupDTO)); + groupNeo4JRepository.save(groupMapper.toNeo4JEntity(groupDTO)); + + return groupMapper.toDTO(saved); + } + + public void deleteGroup(String id) { + getGroupOrThrow(id); + userService.checkUserHasPermissionToEditGroup(id); + + groupRepository.deleteById(id); + groupNeo4JRepository.deleteGroup(id); + } + + public List findGroupsByOrganizerId(String userId) { + return groupRepository.findGroupsByOrganizerId(userId); + } + + private Group getGroupOrThrow(String id) { + return groupRepository.findById(id) + .orElseThrow(() -> new ResponseStatusException(NOT_FOUND, "Event not found: " + id)); + } + + public List findGroupsOfUser(String userId) { + return groupNeo4JRepository.findAllGroupsOfUser(userId).stream() + .map(e -> groupMapper.toNeo4JDTO(e)).toList(); + } + + public List findAllUsersOfGroup(String groupId) { + return userService.findUsersOfGroup(groupId); + } + + public List findAllEventOrganizedByGroup(String groupId) { + return eventService.findEventsOrganizedByGroup(groupId); + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/RecommendationService.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/RecommendationService.java new file mode 100644 index 000000000..c584cce49 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/RecommendationService.java @@ -0,0 +1,76 @@ +package com.example.joinUs.service; + +import com.example.joinUs.Utils; +import com.example.joinUs.dto.GroupDTO; +import com.example.joinUs.dto.summary.EventSummaryDTO; +import com.example.joinUs.mapping.EventMapper; +import com.example.joinUs.mapping.GroupMapper; +import com.example.joinUs.mapping.TopicMapper; +import com.example.joinUs.model.mongodb.User; +import com.example.joinUs.repository.EventNeo4JRepository; +import com.example.joinUs.repository.GroupNeo4JRepository; +import com.example.joinUs.repository.TopicNeo4JRepository; +import com.example.joinUs.repository.UserNeo4JRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + + +@Service +public class RecommendationService { + + @Autowired + private EventNeo4JRepository eventNeo4JRepository; + @Autowired + private GroupNeo4JRepository groupNeo4JRepository; + @Autowired + private UserNeo4JRepository userNeo4JRepository; + @Autowired + private TopicNeo4JRepository topicNeo4JRepository; + + @Autowired + TopicMapper topicMapper; + + @Autowired + EventMapper eventMapper; + + @Autowired + GroupMapper groupMapper; + + public List recommendGroupsBySimilarMembers(int limit) { + User user = Utils.getUserFromContext(); + return groupNeo4JRepository.recommendGroupsBySimilarMembers(user.getId(), limit).stream() + .map(e -> groupMapper.toNeo4JDTO(e)).toList(); + } + + public List recommendGroupsByTopics(int limit) { + + User user = Utils.getUserFromContext(); + + return groupNeo4JRepository.recommendGroupsByTopics(user.getId(), limit) + .stream().map(e -> groupMapper.toNeo4JDTO(e)).toList(); + } + + public List recommendEventsBySharedGroupTopics(int limit) { + User user = Utils.getUserFromContext(); + + return eventNeo4JRepository.recommendEventsBySharedGroupTopics(user.getId(), limit) + .stream().map(e -> eventMapper.toDTOFromNeo4j(e)).toList(); + + } + + public List recommendEventsByMembers(int limit) { + User user = Utils.getUserFromContext(); + + return eventNeo4JRepository.recommendEventsByMembers(user.getId(), limit) + .stream().map(e -> eventMapper.toDTOFromNeo4j(e)).toList(); + } + + public List recommendEventsByPeerGroupAttendance(int limit) { + User user = Utils.getUserFromContext(); + return eventNeo4JRepository.recommendEventsByPeerGroupAttendance(user.getId(), limit) + .stream().map(e -> eventMapper.toDTOFromNeo4j(e)).toList(); + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/UserService.java b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/UserService.java new file mode 100644 index 000000000..29ede588d --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/com/example/joinUs/service/UserService.java @@ -0,0 +1,238 @@ +package com.example.joinUs.service; + +import com.example.joinUs.Utils; +import com.example.joinUs.dto.GroupDTO; +import com.example.joinUs.dto.LoginForm; +import com.example.joinUs.dto.UserDTO; +import com.example.joinUs.dto.summary.EventSummaryDTO; +import com.example.joinUs.mapping.UserMapper; +import com.example.joinUs.model.mongodb.Event; +import com.example.joinUs.model.mongodb.Group; +import com.example.joinUs.model.mongodb.User; +import com.example.joinUs.repository.UserNeo4JRepository; +import com.example.joinUs.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + + +@Service +public class UserService { + + @Autowired + AuthenticationManager authManager; + + @Autowired + private UserRepository userRepository; + + @Autowired + private CityService cityService; + + @Autowired + private GroupService groupService; + + @Autowired + private EventService eventService; + + @Autowired + private UserNeo4JRepository userNeo4JRepo; + + @Autowired + private UserMapper userMapper; + + @Autowired + private PasswordEncoder passwordEncoder; + + public UserDTO getUserProfile() { + User user = Utils.getUserFromContext(); + return userMapper.toDTO(user); + } + + public UserDTO editUserProfile(UserDTO userUpdate) { + + User existingUser = Utils.getUserFromContext(); + String userId = existingUser.getId(); + + if (Utils.isNullOrEmpty(userUpdate.getBio())) userUpdate.setBio(existingUser.getBio()); + if (Utils.isNullOrEmpty(userUpdate.getMemberName())) userUpdate.setMemberName(existingUser.getMemberName()); + if (Utils.isNullOrEmpty(userUpdate.getTopics())) userUpdate.setTopics(existingUser.getTopics()); + if (Utils.isNullOrEmpty(userUpdate.getMemberName())) userUpdate.setMemberName(existingUser.getMemberName()); + if (Utils.isNullOrEmpty(userUpdate.getPassword())) { + userUpdate.setPassword(existingUser.getPassword()); + } else userUpdate.setPassword(passwordEncoder.encode(userUpdate.getPassword())); + + if (userUpdate.getCity() != null) { + cityService.parseCity(userUpdate.getCity()); + existingUser.setCity(userUpdate.getCity()); + } + + //Fields that should not change : + userUpdate.setId(userId); + userUpdate.setEventCount(existingUser.getEventCount()); + userUpdate.setGroupCount(existingUser.getGroupCount()); + userUpdate.setUpcomingEvents(existingUser.getUpcomingEvents()); + + // Changing the approach here to modify the Dto instead of the entity ,so that we can use the DTO for Neo4J too, + // but saving the previous approach to rollback if needed + + // if (userUpdate.getBio() != null) existingUser.setBio(userUpdate.getBio()); + // if (userUpdate.getMemberName() != null) existingUser.setMemberName(userUpdate.getMemberName()); + // if (userUpdate.getPassword()!=null) existingUser.setPassword(passwordEncoder.encode(userUpdate.getPassword())); + + saveUser(userMapper.toEntity(userUpdate)); + userNeo4JRepo.save(userMapper.toNeo4jEntity(userUpdate)); + + return userUpdate; + } + + protected void addUserToGroup(String userId, String groupId) { + userNeo4JRepo.addUserToGroup(userId, groupId); + } + + protected void removeUserFromGroup(String userId, String groupId) { + userNeo4JRepo.removeUserFromGroup(userId, groupId); + } + + public void checkUserHasPermissionToEditGroup(String groupId) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || !authentication.isAuthenticated()) { + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User is not authenticated in the application"); + } + User user = (User) authentication.getPrincipal(); + String userId = user.getId(); + List groups = groupService.findGroupsByOrganizerId(userId); + if (groups == null || groups.isEmpty()) + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "You do not have permission to edit this group"); + + for (Group group : groups) { + if (group.getId().equalsIgnoreCase(groupId)) return; + } + + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "You do not have permission to edit this group"); + + } + + public void checkUserHasPermissionToEditEvent(String eventId) { + + User user = Utils.getUserFromContext(); + String userId = user.getId(); + List groups = groupService.findGroupsByOrganizerId(userId); + if (groups == null) + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "You do not have permission to edit this event"); + + // boolean foundEvent; + for (Group group : groups) { + List events = eventService.findEventsByCreatorGroup(group.getId()); + for (Event event : events) { + if (event.getId().equalsIgnoreCase(eventId)) return; + } + } + throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "You do not have permission to edit this event"); + } + + //TODO follow the same approach as for the editProfile, if we have time + public SecurityContext registerUser(UserDTO userDTO) { + + if (!Utils.isNullOrEmpty(userRepository.findMemberByName(userDTO.getMemberName()))) + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "A user with the username " + userDTO.getMemberName() + " is already registered"); + if (Utils.isNullOrEmpty(userDTO.getPassword())) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Please provide a password"); + } + try { + String password = userDTO.getPassword(); + if (userDTO.getCity() != null) { + cityService.parseCity(userDTO.getCity()); + } + User user = userMapper.toEntity(userDTO); + user.setEventCount(0); + user.setGroupCount(0); + user.setIsAdmin(false); + user.setPassword(passwordEncoder.encode(password)); + user.setUpcomingEvents(new ArrayList<>()); + + if (userDTO.getBio() != null) user.setBio(userDTO.getBio()); + if (userDTO.getMemberName() != null) user.setMemberName(userDTO.getMemberName()); + + if (userDTO.getCity() != null) { + cityService.parseCity(userDTO.getCity()); + user.setCity(userDTO.getCity()); + } + + saveUser(user); + + userNeo4JRepo.save(userMapper.toNeo4jEntity(userDTO)); + + Authentication auth = authManager.authenticate( + new UsernamePasswordAuthenticationToken( + user.getMemberName(), password + ) + ); + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(auth); + SecurityContextHolder.setContext(context); + + return context; + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "Your registration failed " + "\n Error : " + e.getMessage()); + } + + } + + public SecurityContext loginUser(LoginForm loginForm) { + Authentication auth = authManager.authenticate( + new UsernamePasswordAuthenticationToken( + loginForm.userid, loginForm.password + ) + ); + + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(auth); + SecurityContextHolder.setContext(context); + + return context; + + } + + public User saveUser(User user) { + return userRepository.save(user); + } + + public Optional findUserById(String id) { + return userRepository.findById(id); + } + + public void deleteProfile() { + User user = Utils.getUserFromContext(); + userRepository.delete(user); + userNeo4JRepo.deleteUser(user.getId()); + } + + public List findAllEvents() { + User user = Utils.getUserFromContext(); + return eventService.findEventsOfUser(user.getId()); + } + + public List findAllGroups() { + User user = Utils.getUserFromContext(); + return groupService.findGroupsOfUser(user.getId()); + } + + public List findUsersOfGroup(String groupId) { + return userNeo4JRepo.getMembersLinkedToGroup(groupId).stream() + .map(e -> userMapper.toNeo4jDTO(e)).toList(); + } +} + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/ConfigurationFileReader.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/ConfigurationFileReader.java new file mode 100644 index 000000000..7bb0f0810 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/ConfigurationFileReader.java @@ -0,0 +1,64 @@ +package script_transform_csv_to_mongodb_and_neo4j; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + + +public class ConfigurationFileReader { + + private static final String pathOfApplicationProperties = "src/main/resources/application.properties"; + private static final Properties properties = new Properties(); + + static { + try { + properties.load(new FileInputStream(pathOfApplicationProperties)); + } catch (IOException e) { + throw new RuntimeException("Could not load the application.properties file"); + } + } + + public ConfigurationFileReader() throws IOException { + properties.load(new FileInputStream(pathOfApplicationProperties)); + } + + public static String getMongoUrl() { + return checkAndGetProp("spring.mongodb.uri"); + } + + public static String getMongoDatabase() { + return checkAndGetProp("spring.mongodb.database"); + } + + public static String getNeo4JDatabase() { + return checkAndGetProp("spring.data.neo4j.database"); + } + + public static String getNeo4JURL() { + return checkAndGetProp("spring.neo4j.uri"); + } + + public static String getNeo4JUsername() { + return getProperty("spring.neo4j.authentication.username"); + } + + public static String getNeo4JPassword() { + return getProperty("spring.neo4j.authentication.password"); + } + + public static String getProperty(String property) { + return properties.getProperty(property); + } + + //neo4j.username + public static String checkAndGetProp(String property) { + boolean exists = properties.getProperty(property) != null + && !properties.getProperty(property).isEmpty() + && !properties.getProperty(property).equalsIgnoreCase(""); + + if (!exists) throw new RuntimeException("The property does not exist"); + + return properties.getProperty(property); + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/CsvDataOperations.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/CsvDataOperations.java new file mode 100644 index 000000000..2ec09644f --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/CsvDataOperations.java @@ -0,0 +1,483 @@ +package script_transform_csv_to_mongodb_and_neo4j; + +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.Session; +import com.opencsv.CSVReader; +import com.opencsv.CSVWriter; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + + +public class CsvDataOperations { + + public static final String firstDatasetFolder = ConfigurationFileReader.checkAndGetProp("firstDatasetFolder"); + public static final String secondDatasetFolder = ConfigurationFileReader.checkAndGetProp("secondDatasetFolder"); + public static String transformedDatasetFolder = firstDatasetFolder.substring(0, firstDatasetFolder + .substring(0, firstDatasetFolder.length() - 2).lastIndexOf("\\") + 1) + "TransformedDataset\\"; + public static final String metaMembersPath = secondDatasetFolder + "meta-members.csv"; + public static final String metaEventsPath = secondDatasetFolder + "meta-events.csv"; + public static final String metaGroupsPath = secondDatasetFolder + "meta-groups.csv"; + public static final String membersPath = firstDatasetFolder + "members.csv"; + public static final String groupsPath = firstDatasetFolder + "groups.csv"; + public static final String eventsPath = firstDatasetFolder + "events.csv"; + public static final String topicsPath = firstDatasetFolder + "topics.csv"; + + public static final String memberTopicsPath = firstDatasetFolder + "members_topics.csv"; + public static final String groupTopicsPath = firstDatasetFolder + "groups_topics.csv"; + public static final String transformedMembers = transformedDatasetFolder + "members.csv"; + public static final String transformedGroups = transformedDatasetFolder + "groups.csv"; + public static final String transformedEvents = transformedDatasetFolder + "events.csv"; + public static final String transformedGroupTopics = transformedDatasetFolder + "groups_topics.csv"; + public static final String transformedMemberTopics = transformedDatasetFolder + "member_topics.csv"; + public static double membersLimit = Double.parseDouble(ConfigurationFileReader.checkAndGetProp("membersLimit")); + public static double recordsLimit = Double.parseDouble(ConfigurationFileReader.checkAndGetProp("recordsLimit")); + + static List readColumnValues(String csvPath, String column) throws Exception { + try (CSVReader reader = new CSVReader(new FileReader(csvPath))) { + String[] header = reader.readNext(); + int index = Arrays.asList(header).indexOf(column); + + if (index == -1) + throw new RuntimeException("Column not found: " + column); + + List values = new ArrayList<>(); + String[] row; + while ((row = reader.readNext()) != null) { + values.add(row[index]); + } + return values; + } + } + + static Map buildIdMapping( + String destinationCsv, + String destinationIdColumn, + List newIds, + int maxIds + ) throws Exception { + + Map mapping = new HashMap<>(); + Set seen = new HashSet<>(); + Iterator newIdIterator = newIds.iterator(); + + try (CSVReader reader = new CSVReader(new FileReader(destinationCsv))) { + String[] header = reader.readNext(); + int idIndex = Arrays.asList(header).indexOf(destinationIdColumn); + + String[] row; + while ((row = reader.readNext()) != null && newIdIterator.hasNext()) { + String oldId = row[idIndex]; + + if (!seen.contains(oldId)) { + mapping.put(oldId, newIdIterator.next()); + seen.add(oldId); + + if (maxIds > 0 && mapping.size() >= maxIds) + break; + } + } + } + return mapping; + } + + public static Map updateIdsFromCsv( + String sourceCsv, + String sourceIdColumn, + String destinationIdColumn, + int maxIds, + String... destinationCsvs + ) throws Exception { + + if (destinationCsvs == null || destinationCsvs.length == 0) + throw new RuntimeException("No destination CSVs provided"); + + Files.createDirectories(Path.of(transformedDatasetFolder)); + + // 1. Read new IDs (single-threaded) + List newIds = readColumnValues(sourceCsv, sourceIdColumn); + + // 2. Build mapping (single-threaded) + Map mapping = buildIdMapping( + destinationCsvs[0], + destinationIdColumn, + newIds, + maxIds + ); + + Map sameFileReplacements; + try (ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())) { + List> futures = new ArrayList<>(); + + sameFileReplacements = new HashMap<>(); + + // 3. Parallel CSV processing + for (String csv : destinationCsvs) { + String output = transformedDatasetFolder + Path.of(csv).getFileName(); + + boolean sameFile = csv.equalsIgnoreCase(output); + if (sameFile) { + output = output.replace(".csv", "_out.csv"); + sameFileReplacements.put(csv, output); + } + + String finalOutput = output; + + futures.add(executor.submit(() -> { + try { + applyMapping(csv, finalOutput, destinationIdColumn, mapping); //mapping is old,new + } catch (Exception e) { + throw new RuntimeException(e); + } + })); + } + + // 4. Wait for ALL tasks + for (Future future : futures) { + future.get(); // <-- THIS is the correct wait + } + + executor.shutdown(); + } + + // 5. Replace original files (single-threaded, safe) + for (Map.Entry entry : sameFileReplacements.entrySet()) { + Files.delete(Path.of(entry.getKey())); + Files.move(Path.of(entry.getValue()), Path.of(entry.getKey())); + } + + return mapping; + } + + static void applyMapping( + String inputCsv, + String outputCsv, + String idColumn, + Map mapping + ) throws Exception { + + try ( + CSVReader reader = new CSVReader(new FileReader(inputCsv)); + + CSVWriter writer = new CSVWriter(new FileWriter(outputCsv)) + ) { + + String[] header = reader.readNext(); + String[] headerToWrite = new String[header.length]; + + for (int i = 0; i < header.length; i++) + headerToWrite[i] = header[i].replace(".", "_"); //Go back here + + writer.writeNext(headerToWrite); + + int index = Arrays.asList(header).indexOf(idColumn); + + String[] row; + int count = 0; + while ((row = reader.readNext()) != null) { + if ((inputCsv.contains("members") && (membersLimit > 0 && count > membersLimit)) || + (recordsLimit > 0 && count > recordsLimit)) break; + count++; + String oldId = row[index]; + if (mapping.containsKey(oldId)) { + row[index] = mapping.get(oldId); + } + writer.writeNext(row); + } + } + } + + public static void updateMemberIdsAtCSV() throws Exception { + CsvDataOperations.updateIdsFromCsv(CsvDataOperations.metaMembersPath, "member_id", "member_id", -1, + CsvDataOperations.membersPath, CsvDataOperations.memberTopicsPath); + CsvDataOperations.updateIdsFromCsv(CsvDataOperations.transformedMembers, "member_id", "organizer.member_id", -1, + CsvDataOperations.groupsPath); + + // CsvDataOperations.updateIdsFromCsv(CsvDataOperations.transformedMembers,"member_name","organizer.name",-1, CsvDataOperations.transformedGroups); + + } + + public static void updateEventIdsAtCSV() throws Exception { + CsvDataOperations.updateIdsFromCsv(CsvDataOperations.metaEventsPath, "event_id", "event_id", -1, + CsvDataOperations.eventsPath); + } + + public static void updateGroupIdsAtCSV() throws Exception { + CsvDataOperations.updateIdsFromCsv(CsvDataOperations.metaGroupsPath, "group_id", "group_id", -1, + CsvDataOperations.transformedGroups, transformedDatasetFolder + "members.csv", + CsvDataOperations.groupTopicsPath, CsvDataOperations.transformedEvents); + + } + + public static void updateIdsDirectlyFromCSV() { + + try { + CsvDataOperations.updateEventIdsAtCSV(); + CsvDataOperations.updateMemberIdsAtCSV(); + CsvDataOperations.updateGroupIdsAtCSV(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void copyFilesToNeo4JImportFolder() throws IOException { + String transferMode = ConfigurationFileReader.checkAndGetProp("transferMode"); // "local" or "scp" + String importFolder = ConfigurationFileReader.checkAndGetProp("importFolder"); + + File[] files = new File(CsvDataOperations.transformedDatasetFolder).listFiles(); + if (files == null) { + throw new IOException("Transformed dataset folder is empty or doesn't exist."); + } + + if ("local".equalsIgnoreCase(transferMode)) { + // Local copying + for (File file : files) { + String fileName = file.getName(); + Files.copy(file.toPath(), new FileOutputStream(importFolder + fileName)); + } + Files.copy(Path.of(topicsPath), new FileOutputStream(importFolder + "topics.csv")); + Files.copy(Path.of(CsvDataOperations.secondDatasetFolder + "rsvps.csv"), + new FileOutputStream(importFolder + "rsvps.csv")); + } else if ("scp".equalsIgnoreCase(transferMode)) { + // Remote file uploading via SCP + String host = ConfigurationFileReader.checkAndGetProp("scpHost"); + int port = Integer.parseInt(ConfigurationFileReader.checkAndGetProp("scpPort")); // e.g., 22 + String username = ConfigurationFileReader.checkAndGetProp("scpUsername"); + String password = ConfigurationFileReader.checkAndGetProp("scpPassword"); + String remoteDestination = ConfigurationFileReader.checkAndGetProp( + "scpDestination"); // Remote destination directory + + JSch jsch = new JSch(); + Session session = null; + ChannelSftp channelSftp = null; + + try { + // Set up the session + session = jsch.getSession(username, host, port); + session.setPassword(password); + Properties config = new Properties(); + config.put("StrictHostKeyChecking", "no"); + session.setConfig(config); + session.connect(); + + // Open an SFTP channel + channelSftp = (ChannelSftp) session.openChannel("sftp"); + channelSftp.connect(); + + // Upload transformed dataset files + for (File file : files) { + String remoteFilePath = remoteDestination + "/" + file.getName(); + try (InputStream fileInputStream = new FileInputStream(file)) { + channelSftp.put(fileInputStream, remoteFilePath); + } + } + + // Upload topics.csv and rsvps.csv + try (InputStream topicsStream = Files.newInputStream(Path.of(topicsPath)); + InputStream rsvpsStream = Files.newInputStream( + Path.of(CsvDataOperations.secondDatasetFolder + "rsvps.csv"))) { + channelSftp.put(topicsStream, remoteDestination + "/topics.csv"); + channelSftp.put(rsvpsStream, remoteDestination + "/rsvps.csv"); + } + + } catch (Exception e) { + throw new IOException("Error occurred during SCP file transfer: " + e.getMessage(), e); + } finally { + // Cleanup: disconnect the SFTP channel and session + if (channelSftp != null && channelSftp.isConnected()) { + channelSftp.disconnect(); + } + if (session != null && session.isConnected()) { + session.disconnect(); + } + } + } else { + throw new IllegalArgumentException( + "Invalid transferMode specified in the configuration. Use 'local' or 'scp'."); + } + } + // public static void copyFilesToNeo4JImportFolder() throws IOException { + // String transferMode = ConfigurationFileReader.checkAndGetProp("transferMode"); // "local" or "ftp" + // String importFolder = ConfigurationFileReader.checkAndGetProp("importFolder"); + // + // File[] files = new File(CsvDataOperations.transformedDatasetFolder).listFiles(); + // if (files == null) { + // throw new IOException("Transformed dataset folder is empty or doesn't exist."); + // } + // + // if ("local".equalsIgnoreCase(transferMode)) { + // // Local copying + // for (File file : files) { + // String fileName = file.getName(); + // Files.copy(file.toPath(), new FileOutputStream(importFolder + fileName)); + // } + // Files.copy(Path.of(topicsPath), new FileOutputStream(importFolder + "topics.csv")); + // Files.copy(Path.of(CsvDataOperations.secondDatasetFolder + "rsvps.csv"), + // new FileOutputStream(importFolder + "rsvps.csv")); + // } else if ("ftp".equalsIgnoreCase(transferMode)) { + // // FTP file transfer + // String host = ConfigurationFileReader.checkAndGetProp("ftpHost"); + // int port = Integer.parseInt(ConfigurationFileReader.checkAndGetProp("ftpPort")); // Default: 21 + // String username = ConfigurationFileReader.checkAndGetProp("ftpUsername"); + // String password = ConfigurationFileReader.checkAndGetProp("ftpPassword"); + // String remoteDestination = ConfigurationFileReader.checkAndGetProp("ftpDestination"); + // + // FTPClient ftpClient = new FTPClient(); + // + // try { + // // Connect to FTP server + // ftpClient.connect(host, port); + // ftpClient.login(username, password); + // + // // Set FTP transfer mode + // ftpClient.enterLocalPassiveMode(); + // ftpClient.setFileType(FTP.BINARY_FILE_TYPE); + // + // // Upload transformed dataset files + // for (File file : files) { + // String remoteFilePath = remoteDestination + "/" + file.getName(); + // try (InputStream fileInputStream = new FileInputStream(file)) { + // boolean success = ftpClient.storeFile(remoteFilePath, fileInputStream); + // if (!success) { + // throw new IOException("Failed to upload file: " + file.getName()); + // } + // } + // } + // + // // Upload topics.csv and rsvps.csv + // try (InputStream topicsStream = Files.newInputStream(Path.of(topicsPath)); + // InputStream rsvpsStream = Files.newInputStream(Path.of(CsvDataOperations.secondDatasetFolder + "rsvps.csv"))) { + // if (!ftpClient.storeFile(remoteDestination + "/topics.csv", topicsStream)) { + // throw new IOException("Failed to upload topics.csv"); + // } + // if (!ftpClient.storeFile(remoteDestination + "/rsvps.csv", rsvpsStream)) { + // throw new IOException("Failed to upload rsvps.csv"); + // } + // } + // + // } catch (Exception e) { + // throw new IOException("Error occurred during FTP file transfer: " + e.getMessage(), e); + // } finally { + // // Disconnect from FTP server + // if (ftpClient.isConnected()) { + // try { + // ftpClient.logout(); + // ftpClient.disconnect(); + // } catch (IOException ex) { + // System.err.println("Error while disconnecting from FTP server: " + ex.getMessage()); + // } + // } + // } + // } else { + // throw new IllegalArgumentException("Invalid transferMode specified in the configuration. Use 'local' or 'ftp'."); + // } + // } + + + class CsvMemberUpdater { + + void updateMembersTopicsCsv( + String inputPath, + String outputPath, + Map idMap + ) throws Exception { + + try ( + CSVReader reader = new CSVReader(new FileReader(inputPath)); + CSVWriter writer = new CSVWriter(new FileWriter(outputPath)) + ) { + String[] header = reader.readNext(); + writer.writeNext(header); + + int memberIdIndex = Arrays.asList(header).indexOf("member_id"); + + String[] row; + while ((row = reader.readNext()) != null) { + String oldId = row[memberIdIndex]; + + if (idMap.containsKey(oldId)) { + row[memberIdIndex] = idMap.get(oldId); + } + + writer.writeNext(row); + } + } + } + + void transformMemberAndMemberTopicsData() throws Exception { + List newIds = loadMetaMemberIds(metaMembersPath); + + Map idMap = updateMembersCsv( + membersPath, + firstDatasetFolder + "members_transformed.csv", + newIds + ); + + updateMembersTopicsCsv( + memberTopicsPath, + firstDatasetFolder + "members_topics_transformed.csv", + idMap + ); + } + + Map updateMembersCsv( + String membersPath, + String outputPath, + List newIds + ) throws Exception { + + Map idMap = new HashMap<>(); + Set seenOldIds = new HashSet<>(); + Iterator newIdIterator = newIds.iterator(); + + try ( + CSVReader reader = new CSVReader(new FileReader(membersPath)); + CSVWriter writer = new CSVWriter(new FileWriter(outputPath)) + ) { + String[] header = reader.readNext(); + writer.writeNext(header); + + int memberIdIndex = Arrays.asList(header).indexOf("member_id"); + + String[] row; + while ((row = reader.readNext()) != null) { + String oldId = row[memberIdIndex]; + + if (!seenOldIds.contains(oldId) && newIdIterator.hasNext()) { + String newId = newIdIterator.next(); + idMap.put(oldId, newId); + row[memberIdIndex] = newId; + seenOldIds.add(oldId); + } else if (idMap.containsKey(oldId)) { + row[memberIdIndex] = idMap.get(oldId); + } + + writer.writeNext(row); + } + } + return idMap; + } + + List loadMetaMemberIds(String metaPath) throws Exception { + List ids = new ArrayList<>(); + + try (CSVReader reader = new CSVReader(new FileReader(metaPath))) { + String[] header = reader.readNext(); + + int idIndex = Arrays.asList(header).indexOf("member_id"); + + String[] row; + while ((row = reader.readNext()) != null) { + ids.add(row[idIndex]); + } + } + return ids; + } + } +} \ No newline at end of file diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/DataTransformerScript.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/DataTransformerScript.java new file mode 100644 index 000000000..750e8c3d1 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/DataTransformerScript.java @@ -0,0 +1,47 @@ +package script_transform_csv_to_mongodb_and_neo4j; + +import script_transform_csv_to_mongodb_and_neo4j.mongoDb.MongoDataLoader; +import script_transform_csv_to_mongodb_and_neo4j.neo4j.Neo4JDataLoader; +import script_transform_csv_to_mongodb_and_neo4j.neo4j.Neo4JOperations; + +import java.util.Date; +import java.util.concurrent.Future; + + +public class DataTransformerScript { + + public static void main(String[] args) throws Exception { + + long startTime = System.currentTimeMillis(); + System.out.println("STARTING TIME " + new Date()); + ParallelExecutor parallelExecutor = new ParallelExecutor(); + + CsvDataOperations.updateIdsDirectlyFromCSV(); + + Future future1 = parallelExecutor.submit(() -> { + try { + new MongoDataLoader(parallelExecutor).transformCsvDataToMongoDB(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + + Future future2 = null; + if (Neo4JOperations.transferDataToNeo4J.equalsIgnoreCase("true")) { + CsvDataOperations.copyFilesToNeo4JImportFolder(); + future2 = parallelExecutor.submit(() -> { + try { + new Neo4JDataLoader(parallelExecutor).transformCsvDataToNeo4j(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + future2.get(); + future1.get(); + + parallelExecutor.close(); + long endTime = System.currentTimeMillis(); + System.out.println("THE PROCESS TOOK " + (endTime - startTime) / 1000 + " seconds"); + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/ParallelExecutor.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/ParallelExecutor.java new file mode 100644 index 000000000..fb8363ac8 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/ParallelExecutor.java @@ -0,0 +1,96 @@ +package script_transform_csv_to_mongodb_and_neo4j; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; + + +public class ParallelExecutor implements AutoCloseable { + + private final ExecutorService executor; + + public ParallelExecutor() { + this.executor = Executors.newVirtualThreadPerTaskExecutor(); + } + + public static void getFutures(Future[] futures) { + for (Future future : futures) { + try { + if (future != null) future.get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + } + + public Future submit(Function task, T input) { + return executor.submit(() -> { + + return task.apply(input); + }); + } + + public Future submit(Supplier task) { + return executor.submit(() -> { + return task.get(); + }); + + } + + public Future submit(Function task, String input) { + return executor.submit(() -> { + + return task.apply(input); + }); + + } + + public Future submit(Runnable runnable) { + return executor.submit(runnable); + } + + public void shutdown() { + executor.shutdown(); + } + + // public static void getFutures(List futureList){ + // for (Future future :futureList) { + // try { + // if (future!=null) future.get(); + // } catch (InterruptedException e) { + // throw new RuntimeException(e); + // } catch (ExecutionException e) { + // throw new RuntimeException(e); + // } + // } + // } + public static void getFutures(Iterable futureIterable) { + for (Future future : futureIterable) { + try { + if (future != null) future.get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void close() { + shutdown(); + } + + public Future submit(BiFunction function, T arg1, R arg2) { + return executor.submit(() -> { + + return function.apply(arg1, arg2); + }); + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDataLoader.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDataLoader.java new file mode 100644 index 000000000..34cfc46b3 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDataLoader.java @@ -0,0 +1,529 @@ +package script_transform_csv_to_mongodb_and_neo4j.mongoDb; + +import com.mongodb.client.*; +import com.mongodb.client.model.Filters; +import com.opencsv.CSVReader; +import org.bson.Document; +import org.bson.conversions.Bson; +import script_transform_csv_to_mongodb_and_neo4j.ConfigurationFileReader; +import script_transform_csv_to_mongodb_and_neo4j.CsvDataOperations; +import script_transform_csv_to_mongodb_and_neo4j.ParallelExecutor; + +import java.io.File; +import java.io.FileReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; + +import static script_transform_csv_to_mongodb_and_neo4j.CsvDataOperations.membersLimit; + + +public class MongoDataLoader { + + public static MongoClient client = MongoClients.create(ConfigurationFileReader.getMongoUrl()); + public static final String firstDatasetFolder = ConfigurationFileReader.checkAndGetProp( + "firstDatasetFolder");// PATH OF THE DATASET FOUND BY MOIN + public static final String secondDatasetFolder = ConfigurationFileReader.checkAndGetProp( + "secondDatasetFolder");//PATH OF THE DATASET FOUND BY FLORIAN + public static final MongoDatabase csvDocuments = client.getDatabase("CSV_Documents"); + public static final MongoDatabase newMongoDatabase = client.getDatabase(ConfigurationFileReader.getMongoDatabase()); + private static final int BATCH_SIZE = 1000; + private final ParallelExecutor parallelExecutor; + public static boolean useCsvDataUpdater = true; + + // if true update data directly in CSV before importing , else update through mongodb + + public MongoDataLoader(ParallelExecutor parallelExecutor) { + this.parallelExecutor = parallelExecutor; + } + + public void transformCsvDataToMongoDB() throws Exception { + long startingTime = System.currentTimeMillis(); + + // Step-1 , import all the csv data to MongoDB as it is (replace '.' with '_' in the fields that contain '.' due to issues with MongoDB) + + // + System.out.println("IMPORTING THE CSV DATA INTO MongoDB !!!! "); + + importCsvToMongoDB(firstDatasetFolder, membersLimit); + importCsvToMongoDB(secondDatasetFolder, membersLimit); + + if (useCsvDataUpdater) importCsvToMongoDB(CsvDataOperations.transformedDatasetFolder, membersLimit); + + // + System.out.println("FINISHED IMPORTING THE CSV DATA INTO MongoDB !!!! "); + + // if (!useCsvDataUpdater) updateIdsFromMongoDb(); + + createIndexesForCsvCollections(); + + //Step-2 ,Create the new Collection we will use for our project , in the new database 'JoinUs' + + ArrayList futureArrayList = new ArrayList<>(); + // + System.out.println("NOW READY TO CREATE THE NEW DATABASE !!!! "); + + System.out.println("CREATING THE Events COLLECTION : "); + + startingTime = System.currentTimeMillis(); + + // futureArrayList.add( parallelExecutor.submit(() -> { + // try { + new MongoDbEventOperations(client, newMongoDatabase, parallelExecutor).createEventCollection(); + createIndexesForEventsCollection(); + // } catch (Exception e) { + // throw new RuntimeException(e); + // } + System.out.println("Finished creating Events Collection"); + // })); + + futureArrayList.add(parallelExecutor.submit(() -> { + try { + + new MongoDbUserOperations(client, newMongoDatabase, parallelExecutor) + .createMemberCollection(true); + System.out.println("Finished creating Member Collection"); + createIndexesForMembersCollection(); + } catch (Exception e) { + throw new RuntimeException(e); + } + })); + + futureArrayList.add(parallelExecutor.submit(() -> { + try { + new MongoDbGroupOperations(client, newMongoDatabase, parallelExecutor).createGroupCollection(); + System.out.println("Finished creating Group Collection"); + createIndexesForGroupsCollection(); + } catch (Exception e) { + throw new RuntimeException(e); + } + })); + + futureArrayList.add(parallelExecutor.submit(() -> { + + new MongoDbTopicOperation(client, newMongoDatabase, parallelExecutor).createTopicCollection(); + createIndexesForTopicsCollection(); + } + )); + + futureArrayList.add(parallelExecutor.submit(() -> { + + new MongoDbCityOperation(client, newMongoDatabase, parallelExecutor).createCityCollection(); + createIndexesForCitiesCollections(); + + })); + + ParallelExecutor.getFutures(futureArrayList); + + long endingTime = System.currentTimeMillis(); + + System.out.println("THE PROCESS TOOK " + (endingTime - startingTime) / 1000 + " seconds "); + + } + + public static Document renameField(Document document, String oldField, String newField) { + if (document == null || document.isEmpty()) return document; + Document newDocument = new Document(); + for (String key : document.keySet()) { + if (key.equalsIgnoreCase(oldField)) { + newDocument.append(newField, document.get(key)); + } else { + newDocument.append(key, document.get(key)); + } + } + return newDocument; + } + + public static Map flattenDocument( + Object value, + String parentKey, + String delimiter + ) { + Map flattenedMap = new HashMap<>(); + + if (value instanceof Document document) { + + for (Map.Entry entry : document.entrySet()) { + String newKey = parentKey.isEmpty() + ? entry.getKey() + : parentKey + delimiter + entry.getKey(); + + flattenedMap.putAll( + flattenDocument(entry.getValue(), newKey, delimiter) + ); + } + + } else if (value instanceof List) { + // Same behavior as JsonNode array → store as string + flattenedMap.put(parentKey, value.toString()); + + } else { + // Primitive or other BSON type + flattenedMap.put(parentKey, value); + } + + return flattenedMap; + } + + public void updateIdsFromMongoDb() { + + parallelExecutor.submit(() -> { + + updateIdsForCollection(parallelExecutor, "meta-events.csv", "event_id", + "event_id", "events.csv"); + System.out.println("UPDATED THE COLLECTION : updateIdsForCollection.events"); + + }); + + parallelExecutor.submit(() -> { + MongoDbUserOperations.updateIdsForMembers(); + // + // updateIdsForCollection("meta-members.csv","member_id","member_id","members.csv"); + System.out.println("UPDATED THE COLLECTION : updateIdsForMembers.members"); + // + }); + + parallelExecutor.submit(() -> { + // + updateIdsForCollection(parallelExecutor, "meta-groups.csv", "group_id", + "group_id", "groups.csv", "groups_topics.csv", "events.csv", "members.csv");// + System.out.println("UPDATED THE COLLECTION : updateIdsForCollection.groups"); + + }); + + } + + /** + * @param sourceCollection + * The meta collection from which we will get the id + * @param sourceKey + * @param destinationCollections + * All the collections which need to update the key (id) to what has been chosen from sourceCollection for + * example (meta-groups ---> group_id --> group.csv, groups_topics.csv , events.csv) + * @param destinationKey + * The key needs to have the same name for all the destinationCollections , e.g. group_id + * @return + */ + public static void updateIdsForCollection(ParallelExecutor parallelExecutor, String sourceCollection, + String sourceKey, + String destinationKey, String... destinationCollections) { + MongoCollection sourceColl = MongoDataLoader.csvDocuments.getCollection(sourceCollection); + sourceColl.createIndex(new Document(sourceKey, 1)); + if (destinationCollections == null || destinationCollections.length < 1) + throw new RuntimeException("Please provide a destinationCollection"); + + List destinationColls = new ArrayList<>(); + + for (String destColl : destinationCollections) { + destinationColls.add(MongoDataLoader.csvDocuments.getCollection(destColl)); + } + // MongoCollection destColl= CsvToMongoTransformer.csvDocuments.getCollection(destinationCollection); + + List IdsFromSourceColl = retrieveIds(sourceCollection, sourceKey); + + int totalSourceRecords = IdsFromSourceColl.size(); + List destinationIds = retrieveIds(destinationCollections[0], destinationKey); + int totalDestinationRecords = destinationIds.size(); + + Set chosenRecords = new HashSet<>(); + + Random random = new Random(); + String chosenRecord; + + int idsChosen = 0; + + while (idsChosen < Math.min(totalSourceRecords, totalDestinationRecords)) { + do { + chosenRecord = destinationIds.get(random.nextInt(totalDestinationRecords)); + } + while (chosenRecords.contains(chosenRecord)); + + chosenRecords.add(chosenRecord); + + List threadList = new ArrayList<>(); + //Updating the key for all the collections + // for (MongoCollection destinationCollection : destinationColls) { + + Future[] futures = new Future[destinationCollections.length]; + int i = 0; + for (String destCollection : destinationCollections) { + MongoCollection destinationCollection = MongoDataLoader.csvDocuments.getCollection( + destCollection); + + String finalChosenRecord = chosenRecord; + int finalIdsChosen = idsChosen; + futures[i++] = parallelExecutor.submit(() -> { + destinationCollection.updateMany( + Filters.eq(destinationKey, finalChosenRecord), + new Document("$set", + new Document(destinationKey, IdsFromSourceColl.get(finalIdsChosen)) + ) + ); + }); + } + for (Future future : futures) { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + idsChosen++; + + } + // return chosenRecords.stream().toList(); + } + + public static void createIndexesForCsvCollections() { + MongoCollection metaMembersCollection = MongoDataLoader.csvDocuments.getCollection("meta-members.csv"); + metaMembersCollection.createIndex(new Document("member_id", 1)); + + MongoCollection metaEventsCollection = MongoDataLoader.csvDocuments.getCollection("meta-events.csv"); + metaEventsCollection.createIndex(new Document("event_id", 1)); + + MongoCollection metaGroupsCollection = MongoDataLoader.csvDocuments.getCollection("meta-groups.csv"); + metaGroupsCollection.createIndex(new Document("group_id", 1)); + + MongoCollection membersCollection = MongoDataLoader.csvDocuments.getCollection("members.csv"); + membersCollection.createIndex(new Document("member_id", 1)); + membersCollection.createIndex(new Document("group_id", 1)); + + MongoCollection memberTopicsCollection = MongoDataLoader.csvDocuments.getCollection("members_topics.csv"); + memberTopicsCollection.createIndex(new Document("member_id", 1)); + memberTopicsCollection.createIndex(new Document("topic_id", 1)); + + MongoCollection rsvpsCollection = MongoDataLoader.csvDocuments.getCollection("rsvps.csv"); + rsvpsCollection.createIndex(new Document("member_id", 1)); + rsvpsCollection.createIndex(new Document("event_id", 1)); + rsvpsCollection.createIndex(new Document("group_id", 1)); + + MongoCollection groupsCollection = MongoDataLoader.csvDocuments.getCollection("groups.csv"); + groupsCollection.createIndex(new Document("group_id", 1)); + groupsCollection.createIndex(new Document("group_name", 1)); + groupsCollection.createIndex(new Document("organizer_name", 1)); + groupsCollection.createIndex(new Document("organizer_member_id", 1)); + + MongoCollection groupTopicsCollection = MongoDataLoader.csvDocuments.getCollection("groups_topics.csv"); + groupTopicsCollection.createIndex(new Document("group_id", 1)); + groupTopicsCollection.createIndex(new Document("topic_id", 1)); + // + //group_name + MongoCollection eventsCollection = MongoDataLoader.csvDocuments.getCollection("events.csv"); + eventsCollection.createIndex(new Document("event_id", 1)); + eventsCollection.createIndex(new Document("event_name", 1)); + eventsCollection.createIndex(new Document("group_id", 1)); + + MongoCollection citiesCollection = MongoDataLoader.csvDocuments.getCollection("cities.csv"); + citiesCollection.createIndex(new Document("city", 1)); + citiesCollection.createIndex(new Document("city_id", 1)); + + } + + public static void createIndexesForNewCollections() { + createIndexesForEventsCollection(); + createIndexesForGroupsCollection(); + createIndexesForMembersCollection(); + createIndexesForCitiesCollections(); + createIndexesForCitiesCollections(); + } + + public static void createIndexesForCitiesCollections() { + + MongoCollection citiesCollection = MongoDataLoader.newMongoDatabase.getCollection("cities"); + citiesCollection.createIndex(new Document("name", 1)); + + } + + public static void createIndexesForEventsCollection() { + MongoCollection eventsCollection = MongoDataLoader.newMongoDatabase.getCollection("events"); + eventsCollection.createIndex(new Document("event_id", 1)); + eventsCollection.createIndex(new Document("event_name", 1)); + eventsCollection.createIndex(new Document("creator_group", 1)); + eventsCollection.createIndex(new Document("event_time", 1)); + eventsCollection.createIndex(new Document("fee.amount", 1)); + eventsCollection.createIndex(new Document("venue.city.name", 1)); + + } + + public static void createIndexesForMembersCollection() { + MongoCollection membersCollection = MongoDataLoader.newMongoDatabase.getCollection("members"); + membersCollection.createIndex(new Document("member_id", 1)); + } + + public static void createIndexesForGroupsCollection() { + MongoCollection groupsCollection = MongoDataLoader.newMongoDatabase.getCollection("groups"); + groupsCollection.createIndex(new Document("group_id", 1)); + groupsCollection.createIndex(new Document("group_name", 1)); + groupsCollection.createIndex(new Document("event_count", 1)); + groupsCollection.createIndex(new Document("member_count", 1)); + groupsCollection.createIndex(new Document("category.name", 1)); + groupsCollection.createIndex(new Document("city.name", 1)); + + } + + public static void createIndexesForTopicsCollection() { + MongoCollection topicsCollection = MongoDataLoader.newMongoDatabase.getCollection("topics"); + topicsCollection.createIndex(new Document("topic_id", 1)); + } + + public static List retrieveIds(String collection, String key) { + MongoCollection sourceColl = MongoDataLoader.csvDocuments.getCollection(collection); + List IdsFromSourceColl = new ArrayList<>(); + + MongoCursor mongoCursor = sourceColl.find() + .projection(new Document(key, true).append("_id", false)).cursor(); + if (mongoCursor != null) { + while (mongoCursor.hasNext()) { + IdsFromSourceColl.add(((Document) mongoCursor.next()).get(key, String.class)); + } + } + return IdsFromSourceColl; + } + + public static List retrieveIds(String collection, String key, Bson bson) { + MongoCollection sourceColl = MongoDataLoader.csvDocuments.getCollection(collection); + List IdsFromSourceColl = new ArrayList<>(); + + MongoCursor mongoCursor = sourceColl.find(bson) + .projection(new Document(key, true).append("_id", false)).cursor(); + if (mongoCursor != null) { + while (mongoCursor.hasNext()) { + IdsFromSourceColl.add(((Document) mongoCursor.next()).get(key, String.class)); + } + } + return IdsFromSourceColl; + } + + public static void assignIfFound(Document document, String key, Object value) { + if (value == null) return; + + if (value instanceof Document document1) { + if (document1.isEmpty()) return; + } + + if (value instanceof String val) { + if (val.equalsIgnoreCase("not_found") || val.isEmpty()) return; + } + + document.append(key, value); + } + + //To be used for the creation of Neo4J database ,for easier than working with CSV + public static void verifyMongoDatabaseAndCollections() throws Exception { + AtomicBoolean databaseExists = new AtomicBoolean(false); + client.listDatabaseNames().forEach(e -> { + if (e.equalsIgnoreCase(ConfigurationFileReader.getMongoDatabase())) { + databaseExists.set(true); + } + }); + if (!databaseExists.get()) + throw new Exception("Database" + MongoDataLoader.newMongoDatabase.getName() + " not created in MongoDb." + ); + + AtomicBoolean allCollectionsCreated = new AtomicBoolean(false); + List collectionsToVerify = List.of("events", "members", "topics", "groups"); + + for (String coll : collectionsToVerify) { + allCollectionsCreated.set(false); + client.getDatabase(ConfigurationFileReader.getMongoDatabase()).listCollectionNames().forEach(e -> { + if (e.equalsIgnoreCase(coll)) { + allCollectionsCreated.set(true); + } + }); + if (!allCollectionsCreated.get()) throw new Exception("Collection " + coll + " has not been " + + "created in MongoDb."); + + } + + } + + public static Document flattenDocument(Document document) { + Map flat = + flattenDocument(document, "", "_"); + + return new Document(flat); + } + + public static Document cloneDocument(Document document) { + if (document == null) return null; + Document document1 = new Document(); + for (String key : document.keySet()) { + document1.append(key, document.get(key)); + } + return document1; + } + + /** + * @param csvPath + * folder to find the files to import on MongoDB as they are + * @throws Exception + * The goal of this method is simply to import the csv data on MongoDB , which would still require the + * necessary modifications to make sense for MongoDB. + */ + public static void importCsvToMongoDB( + String csvPath, double membersCsvLimit + ) throws Exception { + + if (filterForImportCsvToMongoDBMethod(csvPath)) return; + + if (!Files.isDirectory(Path.of(csvPath))) { + MongoCollection collection = csvDocuments.getCollection( + csvPath.substring(csvPath.lastIndexOf(File.separator) + 1)); + + try (CSVReader reader = new CSVReader( + new FileReader(csvPath))) { + + String[] header = reader.readNext(); // first line + String[] row; + + List batch = new ArrayList<>(); + + double rowCount = 0; + while ((row = reader.readNext()) != null) { + Document doc = new Document(); + + for (int i = 0; i < header.length; i++) { + doc.append(header[i].contains(".") ? header[i].replace(".", "_") : header[i], + row.length > i ? row[i] : null); + } + + batch.add(doc); + + if (batch.size() == BATCH_SIZE) { + collection.insertMany(batch); + batch = new ArrayList<>(); + } + if (csvPath.contains("members.csv") && (membersCsvLimit > 0 && rowCount >= membersCsvLimit)) break; + rowCount++; + } + if (!batch.isEmpty()) { + collection.insertMany(batch); + } + } + } else { + for (File file : new File(csvPath).listFiles()) { + importCsvToMongoDB(file.toString(), membersCsvLimit); + } + } + } + + public static boolean filterForImportCsvToMongoDBMethod(String csvPath) { + if (!Files.isDirectory(Path.of(csvPath))) { + if (csvPath.contains("edge")) return true; + + if (useCsvDataUpdater && !((new File(csvPath).getParent() + "\\").equalsIgnoreCase( + CsvDataOperations.transformedDatasetFolder))) { + for (File file : new File(CsvDataOperations.transformedDatasetFolder).listFiles()) { + if (file.getName().equalsIgnoreCase(csvPath.substring(csvPath.lastIndexOf("\\") + 1))) { + return true; + } + } + } + + } + return false; + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbCityOperation.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbCityOperation.java new file mode 100644 index 000000000..8c041d766 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbCityOperation.java @@ -0,0 +1,108 @@ +package script_transform_csv_to_mongodb_and_neo4j.mongoDb; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import org.bson.Document; +import script_transform_csv_to_mongodb_and_neo4j.ParallelExecutor; + +import java.util.List; + + +public class MongoDbCityOperation { + + public MongoClient mongoClient; + public MongoDatabase mongoOriginalDatabase; + public static String newCityCollectionName = "cities"; + private final ParallelExecutor parallelExecutor; + + public MongoDbCityOperation(MongoClient mongoClient, MongoDatabase mongoOriginalDatabase, + ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + this.mongoOriginalDatabase = mongoOriginalDatabase; + this.parallelExecutor = parallelExecutor; + } + + public MongoCollection getCityCollection() { + mongoOriginalDatabase.createCollection(newCityCollectionName); + MongoCollection mongoCollection = mongoOriginalDatabase.getCollection(newCityCollectionName); + return mongoCollection; + } + + public MongoDbCityOperation(MongoClient mongoClient, ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + mongoOriginalDatabase = mongoClient.getDatabase("joinUs"); + this.parallelExecutor = parallelExecutor; + } + + protected Document extractCityToEmbedFromId(String cityId) { + MongoCollection cityCollection = MongoDataLoader.csvDocuments.getCollection("cities.csv"); + + Document oldDocument = (Document) cityCollection.find(Filters.eq("city_id", cityId)) + .projection(new Document("_id", 0).append("member_count", 0).append("ranking", 0)).first(); + + return extractCityDocument(oldDocument, "city_id"); + } + + protected static Document extractCityToEmbedFromCityName(String cityName) { + MongoCollection cityCollection = MongoDataLoader.csvDocuments.getCollection("cities.csv"); + Document oldDocument = (Document) cityCollection.find(Filters.eq("city", cityName)) + .projection(new Document("_id", 0)).first(); + + if (oldDocument == null) return new Document(); + + Document newDocument = new Document(); + newDocument.append("name", oldDocument.get("city")); + newDocument.append("city_id", oldDocument.get("city_id")); + newDocument.append("state", oldDocument.get("state")); + newDocument.append("country", oldDocument.get("country")); + // city_id + // name + // state + // country + + return newDocument; + } + + private static Document extractCityDocument(Document oldDocument, String idField) { + if (oldDocument == null || oldDocument.isEmpty()) return new Document(); + Document documentToReturn = new Document(); + List keysToIncludeDirectly = List.of("country" + , "state"); + for (String key : oldDocument.keySet()) { + if (keysToIncludeDirectly.contains(key)) + documentToReturn.append(key, oldDocument.getString(key)); + } + documentToReturn.append("name", oldDocument.get("city")); + + documentToReturn.append(idField, oldDocument.get("city_id")); + + double latitude = Double.parseDouble(oldDocument.getString("latitude")); + double longitude = Double.parseDouble(oldDocument.getString("longitude")); + + documentToReturn.append("latitude", latitude); + documentToReturn.append("longitude", longitude); + + return documentToReturn; + } + + public void createCityCollection() { + MongoCollection newCollection = getCityCollection(); + MongoCollection oldCollection = MongoDataLoader.csvDocuments.getCollection("cities.csv"); + + try (MongoCursor mongoCursor = oldCollection.find().cursor()) { + Document oldDocument; + + while (mongoCursor.hasNext()) { + oldDocument = mongoCursor.next(); + Document finalOldDocument = oldDocument; + parallelExecutor.submit(() -> { + newCollection.insertOne(extractCityDocument(finalOldDocument, "_id")); + }); + } + } + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbEventOperations.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbEventOperations.java new file mode 100644 index 000000000..cdd23ddd7 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbEventOperations.java @@ -0,0 +1,275 @@ +package script_transform_csv_to_mongodb_and_neo4j.mongoDb; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import org.bson.Document; +import script_transform_csv_to_mongodb_and_neo4j.ParallelExecutor; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.concurrent.Future; + + +public class MongoDbEventOperations { + + public MongoClient mongoClient; + public MongoDatabase mongoOriginalDatabase; + public static String newEventCollectionName = "events"; + private final ParallelExecutor parallelExecutor; + + public MongoCollection getNewEventCollection() { + mongoOriginalDatabase.createCollection(newEventCollectionName); + return mongoOriginalDatabase.getCollection(newEventCollectionName); + } + + public MongoDbEventOperations(MongoClient mongoClient, MongoDatabase mongoOriginalDatabase, + ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + this.mongoOriginalDatabase = mongoOriginalDatabase; + this.parallelExecutor = parallelExecutor; + } + + public MongoDbEventOperations(MongoClient mongoClient, MongoDatabase mongoOriginalDatabase, + MongoCollection eventCollection, ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + this.mongoOriginalDatabase = mongoOriginalDatabase; + this.parallelExecutor = parallelExecutor; + } + + public MongoDbEventOperations(MongoClient mongoClient, ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + mongoOriginalDatabase = mongoClient.getDatabase("joinUs"); + this.parallelExecutor = parallelExecutor; + } + + public static Document extractFeeFromEvent(Document oldEvent) { + + Document feeDocument = new Document(); + feeDocument.append("accepts", oldEvent.getString("fee_accepts")); + String amount = oldEvent.getString("fee_amount"); + if (amount != null && !amount.isEmpty() && !amount.isBlank()) { + feeDocument.append("amount", Double.parseDouble(amount)); + } else feeDocument.append("amount", 0); + + String currency = oldEvent.getString("fee_currency"); + + if (!currency.equalsIgnoreCase("not_found")) { + feeDocument.append("currency", currency); + } + feeDocument.append("description", oldEvent.getString("fee_description")); + + String isFeeRequired = oldEvent.getString("fee_required"); + if (isFeeRequired.equalsIgnoreCase("0")) feeDocument.append("isRequired", false); + else feeDocument.append("isRequired", true); + + return feeDocument; + } + + /** + * Joining groups by name here to be safer ,since we are going to change the ids + * + * @param oldEvent + * @return + */ + public Document extractCreatorGroupForEvent(Document oldEvent) { + Document groupDocument = new Document(); + String event_id = oldEvent.getString("event_id"); + MongoCollection groupCollection = MongoDataLoader.csvDocuments.getCollection("groups.csv"); + + String groupName = oldEvent.getString("group_name"); + + Document document = (Document) groupCollection.find(Filters.eq("group_name", groupName)).first(); + + if (document == null) return null; + + groupDocument.append("group_id", document.getString("group_id")); + groupDocument.append("group_name", document.getString("group_name")); + groupDocument.append("thumb_link", document.getString("group_photo_thumb_link")); + + return groupDocument; + + } + + public static Document extractVenueForEvent(Document oldEvent) { + String cityName = oldEvent.getString("venue_city"); + Document venue = new Document(); + + Document city = MongoDbCityOperation.extractCityToEmbedFromCityName(cityName); + venue.append("city", city); + MongoDataLoader.assignIfFound(venue, "address_1", oldEvent.getString("venue_address_1")); + MongoDataLoader.assignIfFound(venue, "address_2", oldEvent.getString("venue_address_2")); + + return venue; + + } + + public static Document extractEventDocument(Document oldEvent) { + + Document event = new Document(); + + List fieldsToIncludeDirectly = List.of( + "description", "event_name"); + + event.append("_id", oldEvent.getString("event_id")); + for (String key : oldEvent.keySet()) { + if (fieldsToIncludeDirectly.contains(key)) { + event.append(key, oldEvent.getString(key)); + } + } + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date created = null; + Date event_time = null; + Date updated = null; + try { + created = simpleDateFormat.parse(oldEvent.getString("created")); + event_time = simpleDateFormat.parse(oldEvent.getString("event_time")); + updated = simpleDateFormat.parse(oldEvent.getString("updated")); + } catch (ParseException e) { + throw new RuntimeException(e); + } + + Instant eventTimeInstant = event_time.toInstant(); + Instant updatedInstant = updated.toInstant(); + Instant createdInstant = created.toInstant(); + + //Adding 9 years to simulate "upcoming events" since currently the latest date in the data is 2018 + //TODO modify accordingly + event_time = Date.from(eventTimeInstant.plus(9 * 365 + 2, ChronoUnit.DAYS)); + updated = Date.from(updatedInstant.plus(9 * 365 + 2, ChronoUnit.DAYS)); + created = Date.from(createdInstant.plus(9 * 365 + 2, ChronoUnit.DAYS)); + + event.append("created", created); + event.append("event_time", event_time); + event.append("updated", updated); + + Long duration = Long.parseLong(oldEvent.getString("duration")); + + event.append("duration", duration / 60); + + return event; + } + + /** + * For now this method gets the category directly from the Group + * + * @param oldEvent + * @return + */ + public static Document extractCategoryForEvent(Document oldEvent) { + Document eventDocument = new Document(); + + MongoCollection groupCollection = MongoDataLoader.csvDocuments.getCollection("groups.csv"); + + Document group = (Document) groupCollection.find(Filters.eq("group_name", oldEvent.getString("group_name"))) + .first(); + + return MongoDbGroupOperations.extractCategoryFromGroup(group); + } + + public HashMap extractMemberCountForEvent() { + HashMap memberCount = new HashMap<>(); + MongoCollection rsvpsCollection = MongoDataLoader.csvDocuments.getCollection("rsvps.csv"); + + List aggregationList = Arrays.asList(new Document("$group", + new Document("_id", "$event_id") + .append("member_count", + new Document("$sum", 1L))), + new Document("$project", + new Document("event_id", "$_id") + .append("member_count", + new Document("$toDouble", "$member_count")))); + + MongoCursor mongoCursor = rsvpsCollection.aggregate(aggregationList).cursor(); + + while (mongoCursor.hasNext()) { + Document document = mongoCursor.next(); + memberCount.put(document.getString("event_id"), + document.getDouble("member_count") != null ? document.getDouble("member_count") : 0); + } + return memberCount; + + } + + public void createEventCollection() { + + MongoCollection newEventCollection = getNewEventCollection(); + + HashMap memberCount = extractMemberCountForEvent(); + + MongoCollection oldEventCollection = MongoDataLoader.csvDocuments.getCollection("events.csv"); + + try (MongoCursor mongoCursor = oldEventCollection.find().cursor()) { + Future[] futures = new Future[5]; + + Document newEvent; + while (mongoCursor.hasNext()) { + Document oldDocument = mongoCursor.next(); + String event_id = oldDocument.getString("event_id"); + String event_name = oldDocument.getString("event_name"); + + if (event_name == null || event_name.isEmpty() || event_name.isBlank()) continue; + + futures[0] = parallelExecutor.submit(MongoDbEventOperations::extractEventDocument, oldDocument); + futures[1] = parallelExecutor.submit(MongoDbEventOperations::extractCategoryForEvent, oldDocument); + futures[2] = parallelExecutor.submit(MongoDbEventOperations::extractFeeFromEvent, oldDocument); + futures[3] = parallelExecutor.submit(MongoDbEventOperations::extractVenueForEvent, oldDocument); + futures[4] = parallelExecutor.submit(e -> extractCreatorGroupForEvent(e), oldDocument); + + newEvent = (Document) futures[0].get(); + + newEvent.append("category", futures[1].get()); + newEvent.append("fee", futures[2].get()); + newEvent.append("venue", futures[3].get()); + + MongoDataLoader.assignIfFound(newEvent, "creator_group", futures[4].get()); + newEvent.append("member_count", memberCount.get(event_id) != null ? memberCount.get(event_id) : 0); + + // newEvent = extractEventDocument(oldDocument); + // CsvToMongoTransformer.assignIfFound(newEvent, "creator_group", extractCreatorGroupForEvent(oldDocument)); + // newEvent.append("categories", extractCategoryForEvent(oldDocument)); + // newEvent.append("fee", extractFeeFromEvent(oldDocument)); + // newEvent.append("venue", extractVenueForEvent(oldDocument)); + // newEvent.append("member_count", memberCount.get(event_id)); + + Document finalNewEvent = newEvent; + parallelExecutor.submit(() -> newEventCollection.insertOne(finalNewEvent)); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static List extractUpcomingEventsToEmbed(List event_ids) { + List upcomingEvents = new ArrayList<>(); + MongoCollection eventCollection = MongoDataLoader.newMongoDatabase.getCollection("events"); + + try (MongoCursor eventCursor = eventCollection.find( + Filters.and( + Filters.in("_id", event_ids) + , Filters.gt("event_time", new Date()) + ) + ).cursor()) { + + while (eventCursor.hasNext()) { + Document documentToEmbed = new Document(); + Document eventDocument = eventCursor.next(); + Date event_time = eventDocument.getDate("event_time"); + + // if (event_time.after(new Date())) { + documentToEmbed.append("event_id", eventDocument.getString("_id")); + documentToEmbed.append("event_name", eventDocument.getString("event_name")); + documentToEmbed.append("event_time", event_time); + upcomingEvents.add(documentToEmbed); + // } + } + } + return upcomingEvents; + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbGroupOperations.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbGroupOperations.java new file mode 100644 index 000000000..5056eec06 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbGroupOperations.java @@ -0,0 +1,409 @@ +package script_transform_csv_to_mongodb_and_neo4j.mongoDb; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import org.bson.Document; +import script_transform_csv_to_mongodb_and_neo4j.ConfigurationFileReader; +import script_transform_csv_to_mongodb_and_neo4j.ParallelExecutor; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.Future; + + +public class MongoDbGroupOperations { + + public MongoClient mongoClient; + public MongoDatabase mongoOriginalDatabase; + public static String newGroupCollectionName = "groups"; + private final ParallelExecutor parallelExecutor; + public static HashMap> groups_per_organizer = new HashMap<>(); + + public MongoCollection getNewGroupsCollection() { + mongoOriginalDatabase.createCollection(newGroupCollectionName); + return mongoOriginalDatabase.getCollection(newGroupCollectionName); + } + + public MongoDbGroupOperations(MongoClient mongoClient, MongoDatabase mongoOriginalDatabase, + ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + this.mongoOriginalDatabase = mongoOriginalDatabase; + this.parallelExecutor = parallelExecutor; + } + + public MongoDbGroupOperations(MongoClient mongoClient, MongoDatabase mongoOriginalDatabase, + MongoCollection eventCollection, ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + this.mongoOriginalDatabase = mongoOriginalDatabase; + this.parallelExecutor = parallelExecutor; + } + + public MongoDbGroupOperations(MongoClient mongoClient, ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + mongoOriginalDatabase = mongoClient.getDatabase(ConfigurationFileReader.getMongoDatabase()); + this.parallelExecutor = parallelExecutor; + } + + public static Document extractCategoryFromGroup(Document oldGroupDocument) { + if (oldGroupDocument == null) { + return new Document(); + } + Document category = new Document(); + + category.append("category_id", oldGroupDocument.getString("category_id")) + .append("name", oldGroupDocument.getString("category_name")); + + return category; + } + + /** + * Adds members from the first dataset groups.csv ("members") and counts members from the second dataset rsvps.csv + */ + public HashMap extractMemberCount() { + + HashMap memberCount = new HashMap<>(); + + int membersFromRsvps = 0; + //rsvps.csv + MongoCollection rsvpsCollection = MongoDataLoader.csvDocuments.getCollection("rsvps.csv"); + + List aggregationList = Arrays.asList( + new Document("$group", + new Document("_id", "$group_id") + .append("member_count", + new Document("$addToSet", "$member_id"))), + new Document("$project", + new Document("group_id", "$_id") + .append("member_count", + new Document("$size", "$member_count")) + .append("total_Member", 1L))); + + try (MongoCursor mongoCursor = rsvpsCollection.aggregate(aggregationList).cursor()) { + + while (mongoCursor.hasNext()) { + Document document = mongoCursor.next(); + + memberCount.put(document.getString("group_id"), Double.parseDouble(document.get("member_count") + "")); + } + } + + MongoCollection membersCollection = MongoDataLoader.csvDocuments.getCollection("members.csv"); + + List aggregationOnMembers = Arrays.asList(new Document("$group", + new Document("_id", "$group_id") + .append("member_count", + new Document("$sum", 1L))), + new Document("$project", + new Document("group_id", "$_id") + .append("member_count", 1L))); + + try (MongoCursor cursor = membersCollection.aggregate(aggregationOnMembers).cursor()) { + while (cursor.hasNext()) { + Document document = cursor.next(); + + if (!memberCount.containsKey(document.getString("group_id"))) { + memberCount.put(document.getString("group_id"), + Double.parseDouble(document.get("member_count") + "")); + } + } + } + + return memberCount; + + } + + public List extractUpcomingEventsFromEvents_csv(String group_id) { + MongoCollection eventsCollection = MongoDataLoader.csvDocuments.getCollection("events.csv"); + List eventIdsFromEvents = new ArrayList<>(); + try (MongoCursor cursor = eventsCollection.find(Filters.eq("group_id", group_id)).cursor()) { + while (cursor.hasNext()) { + String event_id = (cursor.next()).getString("event_id"); + eventIdsFromEvents.add(event_id); + } + } + + return MongoDbEventOperations.extractUpcomingEventsToEmbed(eventIdsFromEvents); + + } + + public List extractUpcomingEventsFromRSVPS_csv(String group_id) { + //rsvps.csv + MongoCollection rsvpsCollection = MongoDataLoader.csvDocuments.getCollection("rsvps.csv"); + List eventIdsFromRsvps = new ArrayList<>(); + try (MongoCursor cursor = rsvpsCollection.find(Filters.eq("group_id", group_id)).cursor()) { + + while (cursor.hasNext()) { + String event_id = (cursor.next()).getString("event_id"); + eventIdsFromRsvps.add(event_id); + } + } + + return MongoDbEventOperations.extractUpcomingEventsToEmbed(eventIdsFromRsvps); + } + + public List extractUpcomingEvents(Document oldGroupDocument) { + + if (oldGroupDocument == null || oldGroupDocument.isEmpty()) return new ArrayList<>(); + + String group_id = oldGroupDocument.getString("group_id"); + + List upcomingEvents = new ArrayList<>(); + upcomingEvents.addAll(extractUpcomingEventsFromRSVPS_csv(group_id)); + upcomingEvents.addAll(extractUpcomingEventsFromEvents_csv(group_id)); + return upcomingEvents; + } + + //We can use this method if we want to do the same as for the user (although we have categories for the aggregations) + public List extractTopicsForGroups(Document oldGroupDocument) { + if (oldGroupDocument == null || oldGroupDocument.isEmpty()) return new ArrayList<>(); + + String group_id = oldGroupDocument.getString("group_id"); + MongoCollection topicCollection = MongoDataLoader.csvDocuments.getCollection("groups_topics.csv"); + + List groupTopics = new ArrayList<>(); + MongoCursor cursor = topicCollection + .find(new Document("group_id", group_id)) + .projection(new Document("topic_id", 1).append("topic_key", 1)) + .cursor(); + while (cursor.hasNext()) { + groupTopics.add((Document) cursor.next()); + } + + return groupTopics; + } + + public static Document extractCityForGroup(Document oldGroupDocument) { + // String group_id=oldGroupDocument.getString("group_id"); + String cityName = oldGroupDocument.getString("city"); + return MongoDbCityOperation.extractCityToEmbedFromCityName(cityName); + } + + public HashMap extractEventCount() { + HashMap eventCounts = new HashMap<>(); + + extractEventCountFromRsvps_csv(eventCounts); + extractEventCountFromEvents_csv(eventCounts); + + return eventCounts; + } + + public void extractEventCountFromRsvps_csv(HashMap eventCount) { + MongoCollection rsvpsCollection = MongoDataLoader.csvDocuments.getCollection("rsvps.csv"); + + List aggregationList = Arrays.asList(new Document("$group", + new Document("_id", "$group_id") + .append("eventCount", + new Document("$addToSet", "$event_id"))), + new Document("$project", + new Document("group_id", "$_id") + .append("eventCount", + new Document("$size", "$eventCount")))); + + try (MongoCursor mongoCursor = rsvpsCollection.aggregate(aggregationList).cursor()) { + + while (mongoCursor.hasNext()) { + Document document = mongoCursor.next(); + eventCount.put(document.getString("group_id"), + Double.parseDouble(String.valueOf(document.get("eventCount")))); + + } + } + } + + public void extractEventCountFromEvents_csv(HashMap eventCount) { + + MongoCollection eventsCollection = MongoDataLoader.csvDocuments.getCollection("events.csv"); + + List aggregationList = List.of( + new Document("$group", + new Document("_id", "$group_id") + .append("total_events", + new Document("$sum", 1L)))); + + try (MongoCursor mongoCursor = eventsCollection.aggregate(aggregationList).cursor()) { + + while (mongoCursor.hasNext()) { + Document document = mongoCursor.next(); + String group_id = document.getString("_id"); + if (!eventCount.containsKey(group_id)) + eventCount.put(group_id, Double.parseDouble(String.valueOf(document.get("total_events")))); + } + } + + } + + public List extractOrganizer(Document oldGroupDocument) { + List organizers = new ArrayList<>(); + String group_id = oldGroupDocument.getString("group_id"); + Document organizer = new Document(); + // String organizerName=oldGroupDocument.getString("organizer_name"); + // organizer.append("member_name",organizerName); + String organizerId = oldGroupDocument.getString("organizer_member_id"); + + MongoCollection mongoCollection = MongoDataLoader.csvDocuments.getCollection("members.csv"); + MongoCursor mongoCursor = mongoCollection.find(Filters.eq("member_id", organizerId)).cursor(); + + if (mongoCursor.hasNext()) { + organizer.append("member_id", organizerId); + organizer.append("member_name", mongoCursor.next().getString("member_name")); + } + // if (groups_per_organizer.containsKey(organizerId)){ + // groups_per_organizer.get(organizerId).add(group_id); + // } + // else { + // groups_per_organizer.put(organizerId,new ArrayList<>()); + // } + + organizers.add(organizer); + + return organizers; + } + + public List extractTopicsPerGroup(String groupId) { + + MongoCollection topicCollection = MongoDataLoader.csvDocuments.getCollection("groups_topics.csv"); + + List groupTopics = new ArrayList<>(); + try (MongoCursor cursor = topicCollection + .find(new Document("group_id", groupId)) + .cursor()) { + while (cursor.hasNext()) { + Document document = cursor.next(); + Document documentToAdd = new Document(); + documentToAdd.append("topic_id", document.getString("topic_id")); + documentToAdd.append("topic_name", document.getString("topic_name")); + groupTopics.add(documentToAdd); + } + } + + return groupTopics; + } + + public static Document extractGroupDocument(Document oldGroupDocument) { + List directKeysToInclude = List.of( + "group_name"); + Document newGroupDocument = new Document(); + for (String key : oldGroupDocument.keySet()) { + if (directKeysToInclude.contains(key)) { + newGroupDocument.append(key, oldGroupDocument.getString(key)); + } + } + newGroupDocument.append("_id", oldGroupDocument.getString("group_id")); + MongoDataLoader.assignIfFound(newGroupDocument, "description", oldGroupDocument.getString("description")); + + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date created = null; + try { + created = simpleDateFormat.parse(oldGroupDocument.getString("created")); + } catch (ParseException e) { + throw new RuntimeException(e); + } + + newGroupDocument.append("created", created); + + // Double rating = Double.parseDouble(oldGroupDocument.getString("rating")); + // Long utcOffset = Long.parseLong(oldGroupDocument.getString("utc_offset")); + // newGroupDocument.append("rating",rating); + // newGroupDocument.append("utc_offset",utcOffset); + + return newGroupDocument; + } + + public static Document extractGroupPhoto(Document oldGroupDocument) { + Document photo = new Document(); + for (String key : oldGroupDocument.keySet()) { + if (key.startsWith("group_photo_")) { + String newKey = key.substring("group_photo_".length()); + if (!newKey.equalsIgnoreCase("type") + && !newKey.equalsIgnoreCase("base_url") + && !newKey.equalsIgnoreCase("photo_id")) { + //group_photo type, base_url and photo + MongoDataLoader.assignIfFound(photo, newKey, oldGroupDocument.getString(key)); + } + } + } + return photo; + } + + public void createGroupCollection() throws Exception { + + MongoCollection groupCollection = getNewGroupsCollection(); + MongoCollection categoryNewCollection = MongoDataLoader.newMongoDatabase.getCollection("categories"); + + List categoriesIncluded = new ArrayList<>(); + + try (MongoCursor mongoCursor = MongoDataLoader.csvDocuments.getCollection("groups.csv") + .find() + .cursor()) { + + HashMap memberCount = extractMemberCount(); + HashMap eventCount = extractEventCount(); + + while (mongoCursor.hasNext()) { + + Document oldGroupDocument = mongoCursor.next(); + Future[] futures = new Future[8]; + Document newGroupDocument = new Document(); + + String group_id = oldGroupDocument.getString("group_id"); + + String category_id = oldGroupDocument.getString("category_id"); + + if (!categoriesIncluded.contains(category_id)) { + String name = oldGroupDocument.getString("category_name"); + Document categoryDocument = new Document(); + categoryDocument.append("_id", category_id); + categoryDocument.append("name", name); + + parallelExecutor.submit(() -> categoryNewCollection.insertOne(categoryDocument)); + categoriesIncluded.add(category_id); + } + + futures[0] = parallelExecutor.submit(MongoDbGroupOperations::extractGroupDocument, oldGroupDocument); + futures[1] = parallelExecutor.submit(MongoDbGroupOperations::extractCityForGroup, oldGroupDocument); + futures[2] = parallelExecutor.submit(MongoDbGroupOperations::extractCategoryFromGroup, + oldGroupDocument); + + futures[3] = parallelExecutor.submit(e -> extractOrganizer(e), oldGroupDocument); + futures[4] = parallelExecutor.submit(e -> extractUpcomingEvents(e), oldGroupDocument); + futures[5] = parallelExecutor.submit(MongoDbGroupOperations::extractGroupPhoto, oldGroupDocument); + futures[6] = parallelExecutor.submit(e -> extractTopicsPerGroup(e), group_id); + + newGroupDocument = (Document) futures[0].get(); + newGroupDocument.append("city", futures[1].get()); + newGroupDocument.append("category", futures[2].get()); + + Document finalNewGroupDocument1 = newGroupDocument; + + newGroupDocument.append("organizers", futures[3].get()); + newGroupDocument.append("topics", new Document()); + newGroupDocument.append("upcoming_events", futures[4].get()); + newGroupDocument.append("group_photo", futures[5].get()); + newGroupDocument.append("event_count", eventCount.get(group_id) != null ? eventCount.get(group_id) : 0); + newGroupDocument.append("member_count", + memberCount.get(group_id) != null ? memberCount.get(group_id) : 0); + newGroupDocument.append("topics", futures[6].get());//might do this if we want + + // newGroupDocument = extractGroupDocument(oldGroupDocument); + // newGroupDocument.append("city", extractCityForGroup(oldGroupDocument)); + // newGroupDocument.append("categories", extractCategoryFromGroup(oldGroupDocument)); + // newGroupDocument.append("event_count", eventCount.get(group_id)); + // newGroupDocument.append("member_count", memberCount.get(group_id)); + // newGroupDocument.append("organizer_members", extractOrganizer(oldGroupDocument)); + // newGroupDocument.append("upcoming_events", extractUpcomingEvents(oldGroupDocument)); + // newGroupDocument.append("group_photo", extractGroupPhoto(oldGroupDocument)); + // newGroupDocument.append("topics",extractTopicsForGroups(oldGroupDocument));//might do this if we want + + Document finalNewGroupDocument = newGroupDocument; + + parallelExecutor.submit(() -> groupCollection.insertOne(finalNewGroupDocument)); + } + } + + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbTopicOperation.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbTopicOperation.java new file mode 100644 index 000000000..f77700ae1 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbTopicOperation.java @@ -0,0 +1,70 @@ +package script_transform_csv_to_mongodb_and_neo4j.mongoDb; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import org.bson.Document; +import script_transform_csv_to_mongodb_and_neo4j.ParallelExecutor; + +import java.util.List; + + +public class MongoDbTopicOperation { + + public MongoClient mongoClient; + public MongoDatabase mongoOriginalDatabase; + public static String newTopicCollectionName = "topics"; + private final ParallelExecutor parallelExecutor; + + public MongoDbTopicOperation(MongoClient mongoClient, MongoDatabase mongoOriginalDatabase, + ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + this.mongoOriginalDatabase = mongoOriginalDatabase; + this.parallelExecutor = parallelExecutor; + } + + public MongoCollection getNewTopicCollection() { + mongoOriginalDatabase.createCollection(newTopicCollectionName); + MongoCollection mongoCollection = mongoOriginalDatabase.getCollection(newTopicCollectionName); + return mongoCollection; + } + + public MongoDbTopicOperation(MongoClient mongoClient, ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + mongoOriginalDatabase = mongoClient.getDatabase("joinUs"); + this.parallelExecutor = parallelExecutor; + } + + public Document extractTopicDocument(Document oldDocument, String idField) { + if (oldDocument == null || oldDocument.isEmpty()) return new Document(); + Document documentToReturn = new Document(); + List keysToIncludeDirectly = List.of("description", "topic_name"); + + documentToReturn.append(idField, oldDocument.getString("topic_id")); + for (String key : oldDocument.keySet()) { + if (keysToIncludeDirectly.contains(key)) + documentToReturn.append(key, oldDocument.getString(key)); + } + + return documentToReturn; + } + + public void createTopicCollection() { + MongoCollection newCollection = getNewTopicCollection(); + MongoCollection oldCollection = MongoDataLoader.csvDocuments.getCollection("topics.csv"); + try (MongoCursor mongoCursor = oldCollection.find().cursor()) { + + Document oldDocument; + + while (mongoCursor.hasNext()) { + oldDocument = mongoCursor.next(); + + Document finalOldDocument1 = oldDocument; + + Document finalOldDocument = oldDocument; + parallelExecutor.submit(() -> newCollection.insertOne(extractTopicDocument(finalOldDocument, "_id"))); + } + } + } +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbUserOperations.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbUserOperations.java new file mode 100644 index 000000000..266294192 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/mongoDb/MongoDbUserOperations.java @@ -0,0 +1,436 @@ +package script_transform_csv_to_mongodb_and_neo4j.mongoDb; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import org.bson.Document; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import script_transform_csv_to_mongodb_and_neo4j.ParallelExecutor; + +import java.util.*; +import java.util.concurrent.Future; + + +public class MongoDbUserOperations { + + public MongoClient mongoClient; + public MongoDatabase mongoOriginalDatabase; + public static String newUserCollectionName = "members"; + public static List metaMembersIds; + private final ParallelExecutor parallelExecutor; + + public MongoCollection getNewUserCollection() { + mongoOriginalDatabase.createCollection(newUserCollectionName); + return mongoOriginalDatabase.getCollection(newUserCollectionName); + } + + public MongoDbUserOperations(MongoClient mongoClient, MongoDatabase mongoOriginalDatabase, + ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + this.mongoOriginalDatabase = mongoOriginalDatabase; + this.parallelExecutor = parallelExecutor; + } + + public MongoDbUserOperations(MongoClient mongoClient, MongoDatabase mongoOriginalDatabase, + MongoCollection userCollection, ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + this.mongoOriginalDatabase = mongoOriginalDatabase; + this.parallelExecutor = parallelExecutor; + } + + public MongoDbUserOperations(MongoClient mongoClient, ParallelExecutor parallelExecutor) { + this.mongoClient = mongoClient; + mongoOriginalDatabase = mongoClient.getDatabase("joinUs"); + this.parallelExecutor = parallelExecutor; + } + + /** + * As we agreed we would embed topics to a member in MongoDB for aggregations purpose although we also have them in + * the GraphDB + * + * @param memberId + * @return + */ + public List extractTopicsPerMember(String memberId) { + + MongoCollection topicCollection = MongoDataLoader.csvDocuments.getCollection("members_topics.csv"); + + List userTopics = new ArrayList<>(); + try (MongoCursor cursor = topicCollection + .find(new Document("member_id", memberId)) + // .projection(new Document("topic_id", 1).append("topic_name", 1).append("_id",0)) + .cursor()) { + while (cursor.hasNext()) { + Document document = cursor.next(); + Document documentToAdd = new Document(); + documentToAdd.append("topic_id", document.getString("topic_id")); + documentToAdd.append("topic_name", document.getString("topic_name")); + userTopics.add(documentToAdd); + } + } + + return userTopics; + } + + public HashMap extractGroupCountPerMember() { + + HashMap groupCount = new HashMap<>(); + + // //rsvps.csv + // MongoCollection rsvpsCollection = CsvToMongoTransformer.csvDocuments.getCollection("rsvps.csv"); + // + // List aggregationList = Arrays.asList(new Document("$group", + // new Document("_id", "$member_id") + // .append("groupCount", + // new Document("$sum", + // new Document("$cond", Arrays.asList(new Document("$ifNull", Arrays.asList("$group_id", false)), 1L, 0L))))), + // new Document("$project", + // new Document("member_id", "$_id") + // .append("groupCount", + // new Document("$toDouble", "$groupCount")) + // .append("_id", 0L))); + // int groupCountFromRsvps = 0; + // long groupCountFromMembers=0; + // try (MongoCursor mongoCursor = rsvpsCollection.aggregate(aggregationList).cursor()) { + // + // if (mongoCursor.hasNext()) { + // Document document = mongoCursor.next(); + // groupCount.put(document.getString("member_id"), document.getDouble("groupCount")); + // } + // } + + List membersAggregationList = Arrays.asList(new Document("$group", + new Document("_id", "$member_id") + .append("groupCount", + new Document("$addToSet", "$group_id"))), + new Document("$project", + new Document("member_id", "$_id") + .append("groupCount", + new Document("$size", "$groupCount"))), + new Document("$project", + new Document("member_id", 1L) + .append("groupCount", + new Document("$toDouble", "$groupCount")))); + + MongoCollection membersCollection = MongoDataLoader.csvDocuments.getCollection("members.csv"); + MongoCursor cursor = membersCollection.aggregate(membersAggregationList).cursor(); + + while (cursor.hasNext()) { + Document document = cursor.next(); + String member_id = document.getString("member_id"); + groupCount.put(member_id, document.getDouble("groupCount")); + } + + return groupCount; + } + + public static HashMap extractEventCountPerMember() { + //rsvps.csv + HashMap eventCount = new HashMap<>(); + MongoCollection rsvpsCollection = MongoDataLoader.csvDocuments.getCollection("rsvps.csv"); + + List aggregationList = Arrays.asList( + new Document("$group", + new Document("_id", "$member_id") + .append("count", + new Document("$sum", 1))), + new Document("$project", + new Document("member_id", "$_id") + .append("event_count", + new Document("$toDouble", "$count")) + )); + + try (MongoCursor mongoCursor = rsvpsCollection.aggregate(aggregationList).cursor()) { + + while (mongoCursor.hasNext()) { + Document document = mongoCursor.next(); + eventCount.put(document.getString("member_id"), document.getDouble("event_count")); + } + } + + return eventCount; + } + + public List extractGroupIdsWhereOrganizer(String memberName) { + List list = new ArrayList<>(); + MongoCollection groupCollection = MongoDataLoader.csvDocuments.getCollection("groups.csv"); + + MongoCursor mongoCursor = groupCollection.find(Filters.eq("organizer_name", memberName)) + .projection(new Document("group_id", 1)) + .cursor(); + while (mongoCursor.hasNext()) { + list.add(mongoCursor.next().getString("group_id")); + } + return list; + } + + /** + * Should call this after modifying the events , so that the event_time makes sense, and thus we avoid returning + * empty lists + * + * @param memberId + * @return + */ + public List extractFutureEventsPerMember(String memberId) { + //rsvps.csv + MongoCollection rsvpsCollection = MongoDataLoader.csvDocuments.getCollection("rsvps.csv"); + List eventIdsFromRsvps = new ArrayList<>(); + List upcomingEvents; + try (MongoCursor cursor = rsvpsCollection.find(Filters.eq("member_id", memberId)).cursor()) { + upcomingEvents = new ArrayList<>(); + if (cursor != null) { + while (cursor.hasNext()) { + String event_id = (cursor.next()).getString("event_id"); + eventIdsFromRsvps.add(event_id); + } + + MongoCollection eventCollection = MongoDataLoader.newMongoDatabase.getCollection("events"); + + MongoCursor eventCursor = eventCollection.find + ( + Filters.and( + Filters.in("_id", eventIdsFromRsvps) + , Filters.gt("event_time", new Date() + ) + ) + ).cursor(); + + if (eventCursor != null) { + while (eventCursor.hasNext()) { + Document documentToEmbed = new Document(); + Document eventDocument = (Document) eventCursor.next(); + Date event_time = eventDocument.getDate("event_time"); + + // if (event_time.after(new Date())) { + + documentToEmbed.append("event_id", eventDocument.getString("_id")); + documentToEmbed.append("event_name", eventDocument.getString("event_name")); + documentToEmbed.append("event_time", event_time); + + upcomingEvents.add(documentToEmbed); + // } + } + } + } + } + return upcomingEvents; + } + + public static Document extractCityDocumentToEmbedPerMember(Document oldDocument) { + + if (oldDocument == null) return new Document(); + String cityName = oldDocument.getString("city"); + return MongoDbCityOperation.extractCityToEmbedFromCityName(cityName); + + } + + public static Document extractMemberDocument(Document oldDocument) { + + Document document = new Document(); + if (oldDocument == null) return document; + document.append("_id", oldDocument.getString("member_id")); + document.append("member_name", oldDocument.getString("member_name")); + MongoDataLoader.assignIfFound(document, "hometown", oldDocument.getString("hometown")); + MongoDataLoader.assignIfFound(document, "bio", oldDocument.getString("bio")); + + return document; + } + + public void createMemberCollection(boolean includeOnlyMembersWithModifiedId) throws Exception { + + List memberIds = metaMembersIds != null ? metaMembersIds : retrieveIdsFromMetaMembers(); + boolean includeFilter = includeOnlyMembersWithModifiedId && !memberIds.isEmpty(); + + MongoCollection oldMemberCollection = MongoDataLoader.csvDocuments.getCollection("members.csv"); + MongoCollection newMemberCollection = getNewUserCollection(); + + Document finalDocument; + + Future[] futures = new Future[5]; + + try (MongoCursor mongoCursor = oldMemberCollection.find().cursor()) { + + futures[0] = parallelExecutor.submit(() -> extractEventCountPerMember()); + futures[1] = parallelExecutor.submit(() -> extractGroupCountPerMember()); + + HashMap groupCount = (HashMap) futures[1].get(); + HashMap eventCount = (HashMap) futures[0].get(); + + List membersWithId = new ArrayList<>(); + Set membersAlreadyEvaluated = new HashSet<>(); + String memberId; + String memberName; + int count = 0; + + while (mongoCursor.hasNext()) { + Document document = mongoCursor.next(); + memberId = document.getString("member_id"); + memberName = document.getString("member_name"); + + String finalMemberId = memberId; + + if (!membersAlreadyEvaluated.contains(memberId)) { + + futures[0] = parallelExecutor.submit(MongoDbUserOperations::extractMemberDocument, document); + + futures[2] = parallelExecutor.submit(MongoDbUserOperations::extractCityDocumentToEmbedPerMember, + document); + futures[1] = parallelExecutor.submit(e -> extractTopicsPerMember(e), memberId); + futures[3] = parallelExecutor.submit(e -> extractFutureEventsPerMember(e), memberId); + String finalMemberName1 = memberName; + + finalDocument = (Document) futures[0].get(); + finalDocument.append("city", futures[2].get()); + + finalDocument.append("upcoming_events", futures[3].get()); + finalDocument.append("topics", futures[1].get()); + + finalDocument.append("event_count", + eventCount.get(memberId) != null ? eventCount.get(memberId) : 0); + + finalDocument.append("password", new BCryptPasswordEncoder().encode(memberName + "_password")); + finalDocument.append("isAdmin", count < 5); + + // List groups_organizer= new ArrayList<>(); + // if (MongoDbGroupOperations.groups_per_organizer.get(memberId)!=null) { + // groups_organizer = MongoDbGroupOperations.groups_per_organizer.get(memberId); + // } + finalDocument.append("group_count", + (groupCount.get(memberId) != null ? groupCount.get(memberId) : 0)); + Document finalDocument1 = finalDocument; + parallelExecutor.submit(() -> newMemberCollection.insertOne(finalDocument1)); + + membersAlreadyEvaluated.add(memberId); + count++; + } + + } + } + } + + public List getMembersWithMemberId(String member_id) { + List documentList = new ArrayList<>(); + MongoCollection oldMemberCollection = MongoDataLoader.csvDocuments.getCollection("members.csv"); + try (MongoCursor mongoCursor = oldMemberCollection.find(Filters.eq("member_id", member_id)).cursor()) { + while (mongoCursor.hasNext()) { + Document document = (Document) mongoCursor.next(); + documentList.add(document); + } + } + return documentList; + } + + public static List retrieveIdsFromMetaMembers() { + MongoCollection metaMembersCollection = MongoDataLoader.csvDocuments.getCollection("meta-members.csv"); + metaMembersCollection.createIndex(new Document("member_id", 1)); + List IdsFromMetaMembersColl = new ArrayList<>(); + + MongoCursor mongoCursor = metaMembersCollection.find() + .projection(new Document("member_id", true).append("_id", false)).cursor(); + if (mongoCursor != null) { + while (mongoCursor.hasNext()) { + IdsFromMetaMembersColl.add(((Document) mongoCursor.next()).get("member_id", String.class)); + } + } + return IdsFromMetaMembersColl; + } + + public static List retrieveIdsFromMembers(int limit) { + MongoCollection metaMembersCollection = MongoDataLoader.csvDocuments.getCollection("members.csv"); + List IdsFromMetaMembersColl = new ArrayList<>(); + + MongoCursor mongoCursor = metaMembersCollection.find() + .projection(new Document("member_id", true).append("_id", false)).cursor(); + if (mongoCursor != null) { + int count = 0; + while (mongoCursor.hasNext()) { + IdsFromMetaMembersColl.add(((Document) mongoCursor.next()).get("member_id", String.class)); + count++; + if (count >= limit && limit != -1) break; + } + } + return IdsFromMetaMembersColl; + } + + public static List updateIdsForMembers() { + MongoCollection membersCollection = MongoDataLoader.csvDocuments.getCollection("members.csv"); + MongoCollection memberTopicsCollection = MongoDataLoader.csvDocuments.getCollection("members_topics.csv"); + membersCollection.createIndex(new Document("member_id", 1)); + memberTopicsCollection.createIndex(new Document("member_id", 1)); + List IdsFromMetaMembersColl = retrieveIdsFromMetaMembers(); + int totalMetaMembers = IdsFromMetaMembersColl.size(); + // List membersId = retrieveIdsFromMembers(-1); + + int membersChosen = 0; + List aggregationList = Arrays.asList( + new Document("$group", + new Document("_id", "$member_id") + .append("firstMember", + new Document("$first", "$member_id"))), + new Document("$limit", totalMetaMembers)); + + try (MongoCursor mongoCursor = membersCollection.aggregate(aggregationList).cursor()) { + + Set chosenMembers = new HashSet<>(); + chosenMembers.addAll(IdsFromMetaMembersColl); + + int value = -1; + String chosenMember; +/* + do { + chosenRecord = destinationIds.get(random.nextInt(totalDesinationRecords)); + } + while (chosenRecords.contains(chosenRecord)); + + chosenRecords.add(chosenRecord); + */ + while (membersChosen <= totalMetaMembers && mongoCursor.hasNext()) { + String memberIdToUpdate; + do { + memberIdToUpdate = mongoCursor.next().getString("_id"); + } while (chosenMembers.contains(memberIdToUpdate) && mongoCursor.hasNext()); + + chosenMembers.add(memberIdToUpdate); + + String finalMemberIdToUpdate = memberIdToUpdate; + int finalMembersChosen = membersChosen; + Thread thread1 = new Thread(() -> { + membersCollection.updateMany( + Filters.eq("member_id", finalMemberIdToUpdate), + new Document("$set", + new Document("member_id", IdsFromMetaMembersColl.get(finalMembersChosen)) + ) + ); + }); + String finalMemberIdToUpdate1 = memberIdToUpdate; + int finalMembersChosen1 = membersChosen; + Thread thread2 = new Thread(() -> { + memberTopicsCollection.updateMany(Filters.eq("member_id", finalMemberIdToUpdate1), + new Document("$set", + new Document("member_id", IdsFromMetaMembersColl.get(finalMembersChosen1)) + )); + }); + thread1.start(); + thread2.start(); + thread1.join(); + thread2.join(); + + membersChosen++; + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + metaMembersIds = IdsFromMetaMembersColl; + return IdsFromMetaMembersColl; + } + + // public static long countUpdatedMember(){ + // List list = retrieveIdsFromMetaMembers(); + // MongoCollection mongoCollection = MongoDataLoader.csvDocuments.getCollection("members.csv"); + // long count = mongoCollection.countDocuments(Filters.in("member_id",list)); + // return count; + // } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/neo4j/MongoDbToNeo4j.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/neo4j/MongoDbToNeo4j.java new file mode 100644 index 000000000..6dcba24d0 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/neo4j/MongoDbToNeo4j.java @@ -0,0 +1,325 @@ +//package script_transform_csv_to_mongodb_and_neo4j.neo4j; +// +//import com.mongodb.client.MongoClient; +//import com.mongodb.client.MongoCollection; +//import com.mongodb.client.MongoCursor; +//import com.mongodb.client.MongoDatabase; +//import org.bson.Document; +//import org.neo4j.driver.QueryConfig; +//import script_transform_csv_to_mongodb_and_neo4j.mongoDb.CsvToMongoTransformer; +// +//import java.util.List; +//import java.util.Map; +// +//public class MongoDbToNeo4j { +// public static MongoClient mongoClient; +// public static MongoDatabase mongoDatabase; +// public static MongoDatabase mongoCSVDatabase; +// +// protected void createGroupEventsEdges() { +// MongoCollection collection = CsvToMongoTransformer.newMongoDatabase.getCollection("events"); +// try (MongoCursor mongoCursor = collection +// .find(new Document("creator_group.id", new Document("$exists", true))).cursor()) { +// +// String group_id; +// String event_id; +// +// while (mongoCursor.hasNext()) { +// Document document = mongoCursor.next(); +// +// group_id = document.getEmbedded(List.of("creator_group","id"),String.class); +// event_id = document.getString("event_id"); +// +// createGroupEventEdge(group_id,event_id); +// } +// } +// } +// +// +// +// private void createNodesByMongoCursor(MongoCursor mongoCursor, String nodesName, boolean flattenTheDocument) { +// while (mongoCursor.hasNext()) { +// Document document = mongoCursor.next(); +// document = flattenTheDocument ? CsvToMongoTransformer.flattenDocument(document) : document; +// String queryForNeo4j = "CREATE (p:" + nodesName + " " +docToNeo4jNode(document) + " )"; +// executeQueryInParallel( +// driver.executableQuery(queryForNeo4j).withConfig(QueryConfig.builder().withDatabase(neo4jDatabase).build()) +/// / .execute(); +// ); +// } +// } +// +// private void createNodesByCollectionName(String collectionName, String nodesName, List fieldsToInclude, boolean flattenTheDocument) { +// MongoCollection collection = mongoDatabase.getCollection(collectionName); +// Document projection = new Document("_id", false); +// for (String field : fieldsToInclude) projection.append(field, true); +// try (MongoCursor mongoCursor = collection.find() +// .projection(projection).cursor()) { +// createNodesByMongoCursor(mongoCursor, nodesName, flattenTheDocument); +// } +// } +// +// protected void createEventNodes() { +// List fieldsToInclude = List.of("event_id", "event_name", "event_time", "description", "event_url", "venue.city", "fee" +// ); +// createNodesByCollectionName("events", "Event", fieldsToInclude, true); +// createNeo4JIndex("Event","event_id"); +// +// } +// +// protected void createGroupNodes() { +// List fieldsToInclude = List.of("group_id", "group_name", "city", "description", "link"); +// createNodesByCollectionName("groups", "Group", fieldsToInclude, true); +// createNeo4JIndex("Group","group_id"); +// +// } +// +// //Create Member-Group , Member-Event ,Group-Event +// protected void createEdgesFromRsvp() { +// MongoCollection rsvpsCollection = mongoCSVDatabase.getCollection("rsvps.csv"); +// try (MongoCursor mongoCursor = rsvpsCollection.find().cursor()) { +// String member_id; +// String event_id; +// String group_id; +// while (mongoCursor.hasNext()) { +// Document document = mongoCursor.next(); +// member_id = document.getString("member_id"); +// event_id = document.getString("event_id"); +// group_id = document.getString("group_id"); +// +// executeQueryInParallel( +// driver.executableQuery(""" +// MATCH (member:Member {member_id:$member_id}) +// MATCH (event:Event {event_id:$event_id}) +// MATCH (group:Group {group_id:$group_id}) +// CREATE (member)-[:MEMBER_OF]->(group) +// CREATE (group)-[:ORGANIZES]->(event) +// CREATE (member)-[:ATTENDS]->(event) +// CREATE (member)<-[:MEMBER_OF]-(group) +// CREATE (group)<-[:ORGANIZES]-(event) +// CREATE (member)<-[:ATTENDS]-(event) +// """).withConfig(QueryConfig.builder().withDatabase(neo4jDatabase).build()) +// .withParameters(Map.of("member_id", member_id, "event_id", event_id, "group_id", group_id)) +// ); +//// .execute() +// } +// } +// } +// +// +// // +// protected void createMemberTopicsEdges() { +// MongoCollection collecion = mongoCSVDatabase.getCollection("members_topics.csv"); +// try (MongoCursor mongoCursor = collecion.find().cursor()) { +// String member_id; +// String topic_id; +// +// while (mongoCursor.hasNext()) { +// Document document = mongoCursor.next(); +// member_id = document.getString("member_id"); +// topic_id = document.getString("topic_id"); +// +// createMemberTopicEdge(member_id,topic_id); +// } +// } +// } +// +// +// // +// protected void createGroupTopicsEdges() { +// MongoCollection collection = mongoCSVDatabase.getCollection("groups_topics.csv"); +// try (MongoCursor mongoCursor = collection.find().cursor()) { +// String group_id; +// String topic_id; +// +// while (mongoCursor.hasNext()) { +// Document document = mongoCursor.next(); +// group_id = document.getString("group_id"); +// topic_id = document.getString("topic_id"); +// +// createGroupTopicEdge(group_id,topic_id); +// } +// } +// } +// +// /** +// * This is extra to the edges created from Rsvps.csv , this is based on original members.csv data +// */ +// protected void createGroupMemberEdges() { +// MongoCollection collection = mongoCSVDatabase.getCollection("members.csv"); +// try (MongoCursor mongoCursor = collection.find().cursor()) { +// String group_id; +// String member_id; +// +// while (mongoCursor.hasNext()) { +// Document document = mongoCursor.next(); +// +// group_id = document.getString("group_id"); +// member_id = document.getString("member_id"); +// +// createMemberGroupEdge(member_id,group_id); +// } +// } +// } +// /** +// * This is extra to the edges created from Rsvps.csv , this is based on group_id contained in original events.csv data +// */ +// protected void createGroupEventsEdges() { +// MongoCollection collection = CsvToMongoTransformer.newMongoDatabase.getCollection("events"); +// try (MongoCursor mongoCursor = collection +// .find(new Document("creator_group.id", new Document("$exists", true))).cursor()) { +// +// String group_id; +// String event_id; +// +// while (mongoCursor.hasNext()) { +// Document document = mongoCursor.next(); +// +// group_id = document.getEmbedded(List.of("creator_group","id"),String.class); +// event_id = document.getString("event_id"); +// +// createGroupEventEdge(group_id,event_id); +// } +// } +// } +// protected void createMemberNodes() { +// List fieldsToInclude = List.of("member_id", "bio", "member_name", "member_status", "city", "hometown"); +// createNodesByCollectionName("members", "Member", fieldsToInclude, true); +// createNeo4JIndex("Member","member_id"); +//// MongoCollection membersCollection = mongoDatabase.getCollection("members"); +//// MongoCursor mongoCursor = membersCollection.find() +//// .projection(new Document("member_id",true).append("member_name",true) +//// .append("member_status",true).append("hometown",true).append("link",true) +//// .append("bio",true).append("city",true).append("city.localized_country_name",true).append("_id",false)).cursor(); +//// +//// HashMap mapForGraph = new HashMap<>(); +//// while (mongoCursor.hasNext()){ +//// Document memberDocument = mongoCursor.next(); +//// String queryForNeo4j = "CREATE (:Member "+CsvToMongoTransformer.flattenDocument(memberDocument).toJson()+")"; +//// driver.executableQuery(queryForNeo4j); +//// } +//// return true; +// } +// +// protected void createTopicNodes() { +// List fieldsToInclude = List.of("topic_id", "description", "link", "topic_name"); +// createNodesByCollectionName("topics", "Topic", fieldsToInclude, true); +// createNeo4JIndex("Topic","topic_id"); +// } +// +// +// +// private void createNodesByMongoCursor(MongoCursor mongoCursor, String nodesName, boolean flattenTheDocument) { +// while (mongoCursor.hasNext()) { +// Document document = mongoCursor.next(); +// document = flattenTheDocument ? CsvToMongoTransformer.flattenDocument(document) : document; +// String queryForNeo4j = "CREATE (p:" + nodesName + " " +docToNeo4jNode(document) + " )"; +// executeQueryInParallel( +// driver.executableQuery(queryForNeo4j).withConfig(QueryConfig.builder().withDatabase(neo4jDatabase).build()) +//// .execute(); +// ); +// } +// } +// +// private void createNodesByCollectionName(String collectionName, String nodesName, List fieldsToInclude, boolean flattenTheDocument) { +// MongoCollection collection = mongoDatabase.getCollection(collectionName); +// Document projection = new Document("_id", false); +// for (String field : fieldsToInclude) projection.append(field, true); +// try (MongoCursor mongoCursor = collection.find() +// .projection(projection).cursor()) { +// createNodesByMongoCursor(mongoCursor, nodesName, flattenTheDocument); +// } +// } +// +// +// +// public static void createGroupNode(Document documentToModify,List fieldsToInclude,boolean flattenTheDocument){ +// List fields ; +// if (fieldsToInclude==null || fieldsToInclude.isEmpty()){ +// fields= List.of("group_id", "group_name", "city", "description", "link"); +// } +// else fields=fieldsToInclude; +// createNodeFromDocument("Group",documentToModify,fields,flattenTheDocument); +// } +// public static void createEventNode(Document documentToModify,List fieldsToInclude,boolean flattenTheDocument){ +// List fields ; +// if (fieldsToInclude==null || fieldsToInclude.isEmpty()){ +// fields= List.of("event_id", "event_name", "event_time", "description", "event_url", "venue.city", "fee" +// ); +// } +// else fields=fieldsToInclude; +// createNodeFromDocument("Event",documentToModify,fields,flattenTheDocument); +// } +// public static void createTopicNode(Document documentToModify,List fieldsToInclude,boolean flattenTheDocument){ +// List fields ; +// if (fieldsToInclude==null || fieldsToInclude.isEmpty()){ +// fields = List.of("topic_id", "description", "link", "topic_name"); +// } +// else fields=fieldsToInclude; +// createNodeFromDocument("Topic",documentToModify,fields,flattenTheDocument); +// } +// public static void createMemberNode(Document documentToModify,List fieldsToInclude,boolean flattenTheDocument){ +// List fields ; +// if (fieldsToInclude==null || fieldsToInclude.isEmpty()){ +// fields = List.of("member_id", "bio", "member_name", "member_status", "hometown"); +// } +// else fields=fieldsToInclude; +// createNodeFromDocument("Member",documentToModify,fields,flattenTheDocument); +// } +// +// public static void createMemberGroupEdge(String member_id,String group_id){ +// executeQueryInParallel( driver.executableQuery(""" +// MATCH (group:Group {group_id:$group_id}) +// MATCH (member:Member {member_id:$member_id}) +// CREATE (member)-[:MEMBER_OF]->(group) +// CREATE (member)<-[:MEMBER_OF]-(group) +// +// """).withConfig(QueryConfig.builder() +// .withDatabase(neo4jDatabase).build()) +// .withParameters(Map.of("group_id", group_id, "member_id", member_id))); +// } +// public static void createGroupTopicEdge(String group_id,String topic_id){ +// executeQueryInParallel( +// driver.executableQuery(""" +// MATCH (group:Group {group_id:$group_id}) +// MATCH (topic:Topic {topic_id:$topic_id}) +// CREATE (group)-[:INTERESTED_IN]->(topic) +// CREATE (group)<-[:INTERESTED_IN]-(topic) +// """).withConfig(QueryConfig.builder().withDatabase(neo4jDatabase).build()) +// .withParameters(Map.of("group_id", group_id, "topic_id", topic_id))); +// } +// public static void createMemberTopicEdge(String member_id,String topic_id){ +// executeQueryInParallel( driver.executableQuery(""" +// MATCH (member:Member {member_id:$member_id}) +// MATCH (topic:Topic {topic_id:$topic_id}) +// CREATE (member)-[:INTERESTED_IN]->(topic) +// CREATE (member)<-[:INTERESTED_IN]-(topic) +// """).withConfig(QueryConfig.builder() +// .withDatabase(neo4jDatabase).build()) +// .withParameters(Map.of("member_id", member_id, "topic_id", topic_id))); +// } +// public static void createGroupEventEdge(String group_id,String event_id){ +// executeQueryInParallel(driver.executableQuery(""" +// MATCH (group:Group {group_id:$group_id}) +// MATCH (event:Event {event_id:$event_id}) +// CREATE (group)<-[:ORGANIZES]-(event) +// CREATE (group)-[:ORGANIZES]->(event) +// """).withConfig(QueryConfig.builder() +// .withDatabase(neo4jDatabase).build()) +// .withParameters(Map.of("group_id", group_id, "event_id", event_id)) +// ); +// } +// public static void createMemberEventEdge(String member_id,String event_id){ +// executeQueryInParallel(driver.executableQuery(""" +// MATCH (member:Member {member_id:$member_id}) +// MATCH (event:Event {event_id:$event_id}) +// CREATE (member)<-[:ATTENDS]-(event) +// CREATE (member)-[:ATTENDS]->(event) +// """).withConfig(QueryConfig.builder() +// .withDatabase(neo4jDatabase).build()) +// .withParameters(Map.of("member_id", member_id, "event_id", event_id)) +// ); +// } +// +// +//} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/neo4j/Neo4JDataLoader.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/neo4j/Neo4JDataLoader.java new file mode 100644 index 000000000..4dcd68c22 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/neo4j/Neo4JDataLoader.java @@ -0,0 +1,127 @@ +package script_transform_csv_to_mongodb_and_neo4j.neo4j; + +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Config; +import org.neo4j.driver.Driver; +import org.neo4j.driver.GraphDatabase; +import script_transform_csv_to_mongodb_and_neo4j.ConfigurationFileReader; +import script_transform_csv_to_mongodb_and_neo4j.ParallelExecutor; + +import java.util.concurrent.Future; + + +public class Neo4JDataLoader { + + private static final String uri = ConfigurationFileReader.getNeo4JURL(); + private static final String dbUserName = ConfigurationFileReader.getNeo4JUsername(); + private static final String dbPassword = ConfigurationFileReader.getNeo4JPassword(); + private static final String defaultDatabase = ConfigurationFileReader.getNeo4JDatabase(); + + private final ParallelExecutor parallelExecutor; + + public Neo4JDataLoader(ParallelExecutor parallelExecutor) { + this.parallelExecutor = parallelExecutor; + } + + public static Driver driver; + + private static void initDriver() { + Config config = Config.builder() + // .withConnectionTimeout(2, TimeUnit.MINUTES) + .withMaxConnectionPoolSize(10) + .build(); + + if (dbUserName != null && !dbUserName.isEmpty() && dbPassword != null && !dbPassword.isEmpty()) { + driver = GraphDatabase.driver(uri, AuthTokens.basic(dbUserName, dbPassword), config); + } else { + driver = GraphDatabase.driver(uri, config); + } + } + + public static Driver getNeo4jDriver() { + if (driver == null) { + initDriver(); + } + return driver; + } + + public void transformCsvDataToNeo4j() throws Exception { + Neo4JOperations neo4JOperations = new Neo4JOperations(getNeo4jDriver(), defaultDatabase, parallelExecutor); + + // System.out.println("CREATING Nodes"); + Future[] futures = new Future[6]; + // + futures[0] = parallelExecutor.submit(() -> { + Neo4JOperations.createMemberNodes(null); + }); + futures[1] = parallelExecutor.submit(() -> { + Neo4JOperations.createEventNodes(null); + }); + futures[2] = parallelExecutor.submit(() -> { + Neo4JOperations.createGroupNodes(null); + }); + futures[3] = parallelExecutor.submit(() -> { + Neo4JOperations.createTopicNodes(null); + }); + // + ParallelExecutor.getFutures(futures); + + Neo4JOperations.createNeo4JIndex("Group", "group_id"); + Neo4JOperations.createNeo4JIndex("Member", "member_id"); + Neo4JOperations.createNeo4JIndex("Event", "event_id"); + Neo4JOperations.createNeo4JIndex("Event", "event_time"); + + Neo4JOperations.createNeo4JIndex("Topic", "topic_id"); + + System.out.println("CREATING Edges "); + futures[2] = parallelExecutor.submit(() -> { + Neo4JOperations.createGroupTopicsEdges(); + }); + futures[3] = parallelExecutor.submit(() -> { + Neo4JOperations.createMemberEventsEdges(); + }); + + futures[2].get(); + futures[3].get(); + System.out.println("FINISHED CREATING Edges Group-Topics and Member-Events "); + + futures[1] = parallelExecutor.submit(() -> { + Neo4JOperations.createGroupEventsEdges(); + }); + futures[4] = parallelExecutor.submit(() -> { + Neo4JOperations.createMemberTopicsEdges(); + }); + futures[1].get(); + futures[4].get(); + System.out.println("FINISHED CREATING Edges Group-Events and Member-Topics "); + + futures[5] = parallelExecutor.submit(() -> { + Neo4JOperations.createMemberGroupsEdge(); + }); + System.out.println("FINISHED CREATING Edges Group-Members "); + + ParallelExecutor.getFutures(futures); + System.out.println("Finished CREATING Edges AND All the process for Neo4J"); + + driver.close(); + + } +} + + + + + + + + + + + + + + + + + + diff --git a/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/neo4j/Neo4JOperations.java b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/neo4j/Neo4JOperations.java new file mode 100644 index 000000000..6d65ef187 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/java/script_transform_csv_to_mongodb_and_neo4j/neo4j/Neo4JOperations.java @@ -0,0 +1,364 @@ +package script_transform_csv_to_mongodb_and_neo4j.neo4j; + +import com.opencsv.CSVReader; +import org.bson.Document; +import org.neo4j.driver.*; +import script_transform_csv_to_mongodb_and_neo4j.ConfigurationFileReader; +import script_transform_csv_to_mongodb_and_neo4j.CsvDataOperations; +import script_transform_csv_to_mongodb_and_neo4j.ParallelExecutor; + +import java.io.FileReader; +import java.util.*; + + +public class Neo4JOperations { + + public static Driver driver = Neo4JDataLoader.getNeo4jDriver(); + public static String neo4jDatabase = ConfigurationFileReader.getNeo4JDatabase(); + public static String transferDataToNeo4J = ConfigurationFileReader.checkAndGetProp("transferDataToNeo4J"); + private static final String ImportFolder = "file:///"; + private static final String BATCH_SIZE = ConfigurationFileReader.checkAndGetProp("importBatchSize"); + + private static final List groupProperties = List.of("group_id", "group_name", "city", "description", + "category_name"); + private static final List eventProperties = List.of("event_id", "event_name", "event_time", "description", + "fee_amount", "venue_city", "group_name", "group_id", "venue_address_1"); + private static final List topicProperties = List.of("topic_id", "topic_name"); + private static final List memberProperties = List.of("member_id", "city", "member_name"); + + public Neo4JOperations(Driver driver, String neo4jDatabase, ParallelExecutor parallelExecutor) { + Neo4JOperations.driver = driver; + Neo4JOperations.neo4jDatabase = neo4jDatabase; + } + + private static void createNode(String fileName, String nodeName, List columnsToInclude) { + StringJoiner columnText = new StringJoiner(","); + + String column = null; + + for (String str : columnsToInclude) { + // column = str.replace("___", "_"); + column = str; + + if (column.equalsIgnoreCase("event_time")) { + columnText.add( + "event_time: datetime(replace(row.event_time,' ','T'))" + + " + duration({days: 3287})" + ); + } else { + columnText.add(column + ": row." + column); + } + } + String txt = columnText.toString(); + // .substring(0,columnText.length()-1); + try (Session session = driver.session(SessionConfig.builder().withDatabase(neo4jDatabase).build())) { + session.run(" LOAD CSV WITH HEADERS FROM '" + ImportFolder + fileName + "' AS row " + + "CALL {" + + "WITH row " + + " MERGE (m:" + nodeName + " { " + txt + " })" + + " } IN TRANSACTIONS OF " + BATCH_SIZE + " ROWS;"); + + } + } + + public static void createGroupNodes(List fieldsToInclude) { + List fields; + if (fieldsToInclude == null || fieldsToInclude.isEmpty()) { + fields = groupProperties; + } else fields = fieldsToInclude; + createNode("groups.csv", "Group", fields); + + } + + public static void createEventNodes(List fieldsToInclude) { + List fields; + if (fieldsToInclude == null || fieldsToInclude.isEmpty()) { + fields = eventProperties; + } else fields = fieldsToInclude; + createNode("events.csv", "Event", fields); + } + + public static void createTopicNodes(List fieldsToInclude) { + List fields; + if (fieldsToInclude == null || fieldsToInclude.isEmpty()) { + fields = topicProperties; + } else fields = fieldsToInclude; + createNode("topics.csv", "Topic", fields); + } + + public static void createMemberNodes(List fieldsToInclude) { + + // createNode("members.csv","Member",memberProperties); + + String path = CsvDataOperations.transformedMembers; + try { + CSVReader csvReader = new CSVReader(new FileReader(path)); + + String[] header = csvReader.readNext(); + int memberIdIndex = -1; + int cityIndex = -1; + int memberNameIndex = -1; + + for (int i = 0; i < header.length; i++) { + if (header[i].equalsIgnoreCase("member_id")) memberIdIndex = i; + else if (header[i].equalsIgnoreCase("city")) { + cityIndex = i; + } else if (header[i].equalsIgnoreCase("member_name")) { + memberNameIndex = i; + } + } + + if (memberNameIndex == -1 || cityIndex == -1 || memberIdIndex == -1) + throw new Exception("File does not have the column"); + + String[] row; + Set membersIncluded = new HashSet<>(); + while ((row = csvReader.readNext()) != null) { + + String member_id = row[memberIdIndex]; + + if (!membersIncluded.contains(member_id)) { + String member_name = row[memberNameIndex]; + String city = row[cityIndex]; + + String query = """ + CREATE (m:Member { + member_id: $memberId, + city: $city, + member_name: $memberName + }) + """; + + driver.executableQuery(query) + .withParameters(Map.of( + "memberId", member_id, + "city", city, + "memberName", member_name + )) + .withConfig(QueryConfig.builder() + .withDatabase(neo4jDatabase) + .build()) + .execute(); + membersIncluded.add(member_id); + } + + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + public static void createNeo4JIndex(String nodeName, String indexKey) { + + executeQueryInParallel(driver.executableQuery(" CREATE INDEX " + indexKey + "_index" + + " IF NOT EXISTS " + + " FOR (e:" + nodeName + ")" + + " ON (e." + nodeName + ");").withConfig(QueryConfig.builder() + .withDatabase(neo4jDatabase).build())); + } + + /** + * Function to remove the quotas from the keys + */ + private static String docToNeo4jNode(Document document) { + StringBuilder node = new StringBuilder("{"); + for (String key : document.keySet()) { + node.append(key.replaceAll("\"", "")); + node.append(": "); + node.append("\"" + (document.get(key) != null ? document.get(key).toString() : "") + "\""); + node.append(","); + } + node.deleteCharAt(node.lastIndexOf(",")); + node.append("}"); + return node.toString(); + + } + + private static void executeQueryInParallel(ExecutableQuery executableQuery) { + // parallelExecutor.submit(() -> executableQuery.execute()); + if (transferDataToNeo4J.equalsIgnoreCase("false")) {} + else executableQuery.execute(); + } + + protected static void createMemberGroupsEdge() { + addEdges("members.csv", "member_id", "group_id"); + addEdges("rsvps.csv", "member_id", "group_id"); + } + + protected static void createGroupTopicsEdges() { + addEdges("groups_topics.csv", "group_id", "topic_id"); + } + + protected static void createMemberTopicsEdges() { + addEdges("members_topics.csv", "member_id", "topic_id"); + } + + protected static void createGroupEventsEdges() { + addEdges("rsvps.csv", "group_id", "event_id"); + } + + protected static void createMemberEventsEdges() { + addEdges("rsvps.csv", "member_id", "event_id"); + } + + // private static void createEdgesFromCSV(String filePath,String firstKey,String secondKey){ + // double BATCH_SIZE = 1_000; + // try ( + // Session session = driver.session(); + // CSVReader reader = new CSVReader(new FileReader(filePath)); + // ) { + // List> batch = new ArrayList<>(); + // List batch2=new ArrayList<>(); + // String[] row; + // + // String[] header = reader.readNext(); + // int indexOf_firstKey=-1; + // int index_Of_secondKey=-1; + // for (int i=0;i(e) " + // +" MERGE (m)<-[:"+relationship+"]-(e) " + // + "").withConfig(QueryConfig.builder() + // .withDatabase(neo4jDatabase).build())); + // } + // + // private static void writeBatch(String firstKey,String secondKey, List batch) { + // + // String firstNode=findNode(firstKey); + // String secondNode=findNode(secondKey); + // String relationship = findRelationship(firstKey,secondKey); + // + // try (Session session = driver.session()) { + // session.executeWrite(tx -> + // tx.run( + // "" + // + " UNWIND $rows AS row " + // +" MATCH (m:"+firstNode+" {"+firstKey+": row[0]}) " + // +" MATCH (e:"+secondNode+" {"+secondKey+": row[1]}) " + // +" MERGE (m)-[:"+relationship+"]->(e) " + // +" MERGE (m)<-[:"+relationship+"]-(e) " + // + "", + // Map.of("rows", batch) + // ) + // ); + // } + // } + private static String findRelationship(String firstKey, String secondKey) { + if ((firstKey.equalsIgnoreCase("member_id") + && secondKey.equalsIgnoreCase("group_id")) || + (firstKey.equalsIgnoreCase("group_id") + && secondKey.equalsIgnoreCase("member_id"))) return "MEMBER_OF"; + else if ((firstKey.equalsIgnoreCase("topic_id") + && secondKey.equalsIgnoreCase("group_id")) || + (firstKey.equalsIgnoreCase("group_id") + && secondKey.equalsIgnoreCase("topic_id"))) return "HAS_TOPIC"; + else if ((firstKey.equalsIgnoreCase("event_id") + && secondKey.equalsIgnoreCase("group_id")) || + (firstKey.equalsIgnoreCase("group_id") + && secondKey.equalsIgnoreCase("event_id"))) return "ORGANIZES"; + else if ((firstKey.equalsIgnoreCase("event_id") + && secondKey.equalsIgnoreCase("member_id")) || + (firstKey.equalsIgnoreCase("member_id") + && secondKey.equalsIgnoreCase("event_id"))) return "ATTENDS"; + else if ((firstKey.equalsIgnoreCase("topic_id") + && secondKey.equalsIgnoreCase("member_id")) || + (firstKey.equalsIgnoreCase("member_id") + && secondKey.equalsIgnoreCase("topic_id"))) return "INTERESTED_IN"; + return null; + } + + private static String findNode(String primaryKey) { + if (primaryKey.equalsIgnoreCase("member_id")) return "Member"; + if (primaryKey.equalsIgnoreCase("group_id")) return "Group"; + if (primaryKey.equalsIgnoreCase("event_id")) return "Event"; + if (primaryKey.equalsIgnoreCase("topic_id")) return "Topic"; + + return null; + } + + // private static void createNodeFromDocument(String nodesName,Document documentToModify,List fieldsToInclude,boolean flattenTheDocument){ + // Document document = new Document(); + // for (String key:documentToModify.keySet()){ + // if (fieldsToInclude.contains(key)){ + // document.append(key,documentToModify.get(key)); + // } + // } + // if (flattenTheDocument) document = MongoDataLoader.flattenDocument(document); + // String queryForNeo4j = "CREATE (p:" + nodesName + " " +docToNeo4jNode(document) + " )"; + // executeQueryInParallel( + // driver.executableQuery(queryForNeo4j).withConfig(QueryConfig.builder().withDatabase(neo4jDatabase).build()) + + /// / .execute(); + // ); + // } + private static void addEdges(String fileName, String firstKey, String secondKey) { + String firstNode = findNode(firstKey); + String secondNode = findNode(secondKey); + String relationship = findRelationship(firstKey, secondKey); + + // String str1="" + + // " LOAD CSV WITH HEADERS FROM '"+ ImportFolder +fileName+"' AS row " + + // "CALL {" + + // "WITH row " + // +" MATCH (m:"+firstNode+" { "+firstKey+": row."+firstKey+" }) " + + // " MATCH (e:"+secondNode+" { "+secondKey+": row."+secondKey+" }) " + + // " CREATE (m)-[:"+relationship+"]->(e) "; + + try (Session session = driver.session( + SessionConfig.builder().withDatabase(ConfigurationFileReader.getNeo4JDatabase()).build())) { + + // + " SET m.name = row.name"+ + session.run(" LOAD CSV WITH HEADERS FROM '" + ImportFolder + fileName + "' AS row " + + "CALL {" + + "WITH row " + + " MATCH (m:" + firstNode + " { " + firstKey + ": row." + firstKey + " }) " + + " MATCH (e:" + secondNode + " { " + secondKey + ": row." + secondKey + " }) " + + " CREATE (m)-[:" + relationship + "]->(e) " + + " } IN TRANSACTIONS OF " + BATCH_SIZE + " ROWS;"); +/* ++" MATCH (m:"+firstNode+" {"+firstKey+": row[0]}) " + +" MATCH (e:"+secondNode+" {"+secondKey+": row[1]}) " + +" MERGE (m)-[:"+relationship+"]->(e) " + +" MERGE (m)<-[:"+relationship+"]-(e) " + */ + } + + } + +} diff --git a/jdk_21_maven/cs/rest/joinus/src/main/resources/application.properties b/jdk_21_maven/cs/rest/joinus/src/main/resources/application.properties new file mode 100644 index 000000000..edbc46cae --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/resources/application.properties @@ -0,0 +1,47 @@ +spring.main.allow-circular-references=true +spring.application.name=joinUs +server.port=8080 + +# MongoDB Database Spring Configuration +spring.mongodb.uri=mongodb://mongodb:secure_password@10.1.1.18:27017,10.1.1.19:27017,10.1.1.20:27017/?replicaSet=rs0&authSource=admin&readPreference=nearest +#spring.data.mongodb.host=localhost +#spring.data.mongodb.port=27017 +spring.mongodb.database=joinUs + +#logging.level.org.springframework.data.mongodb.core.MongoTemplate=DEBUG +#logging.level.org.springframework.data.mongodb.repository.query=DEBUG +logging.level.org.mongodb.driver.protocol.command=DEBUG + + +# Neo4J Database Spring Configuration +spring.neo4j.uri=neo4j://10.1.1.18 +spring.data.neo4j.database=neo4j +spring.neo4j.authentication.username=neo4j +spring.neo4j.authentication.password=secure_password + + +# Optional for debugging +logging.level.org.neo4j.driver=DEBUG +logging.level.org.springframework.security=DEBUG + + + +#PROPERTIES FOR LOADING DATA TO MongoDB AND NEO4J +#Datasets Path +firstDatasetFolder=C:\\Users\\Public\\JoinUs_Dataset\\Meetup\\ +secondDatasetFolder=C:\\Users\\Public\\JoinUs_Dataset\\dataset2_joinUs\\ +transformedDatasetFolder=C:\\Users\\Public\\JoinUs_Dataset\\TransformedDataset\\ +transferDataToNeo4J=true +importBatchSize=5000 +membersLimit=363872 +recordsLimit=-1 + +# NEO4J file copying +# transferMode local for local setup, scp for server setup +transferMode=scp +importFolder=C:\\Users\\User\\.Neo4jDesktop2\\Data\\dbmss\\dbms-64f9bf75-f64d-456e-ad79-0159484aceb3\\import\\ +scpHost=10.1.1.18 +scpPort=22 +scpUsername=root +scpPassword=root +scpDestination=/home/deploy/neo4j/import diff --git a/jdk_21_maven/cs/rest/joinus/src/main/resources/templates/login.html b/jdk_21_maven/cs/rest/joinus/src/main/resources/templates/login.html new file mode 100644 index 000000000..0bb993eb2 --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/main/resources/templates/login.html @@ -0,0 +1,151 @@ + + + + + JoinUs • Login + + + + + + + + + + + diff --git a/jdk_21_maven/cs/rest/joinus/src/test/java/com/example/joinUs/JoinUsApplicationTests.java b/jdk_21_maven/cs/rest/joinus/src/test/java/com/example/joinUs/JoinUsApplicationTests.java new file mode 100644 index 000000000..2c96d50aa --- /dev/null +++ b/jdk_21_maven/cs/rest/joinus/src/test/java/com/example/joinUs/JoinUsApplicationTests.java @@ -0,0 +1,14 @@ +package com.example.joinUs; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + + +@SpringBootTest +class JoinUsApplicationTests { + + @Test + void contextLoads() { + } + +}