diff --git a/README.md b/README.md index 7beb955..8e966a0 100755 --- a/README.md +++ b/README.md @@ -55,6 +55,10 @@ if not provided, then SSL connection is not used, direct insecure connection is - TOKEN_CACHE_TIME: Cache time of M2M token, optional - AUTH0_CLIENT_ID: Auth0 client id for M2M token - AUTH0_CLIENT_SECRET: Auth0 client secret for M2M token +- IS_CREATE_FORUM: Should create forum resource or not +- CHALLENGE_ORIGINATOR: originator from challenge service +- CHALLENGE_USER_UNREGISTRATION_TOPIC: unregistration topic from challenge service +- CHALLENGE_USER_REGISTRATION_TOPIC: registration topic from challenge service Also note that there is a `/health` endpoint that checks for the health of the app. This sets up an expressjs server and listens on the environment variable `PORT`. It's not part of the configuration file and needs to be passed as an environment variable diff --git a/challenge-api-v5-mock/mock-challenge-api.js b/challenge-api-v5-mock/mock-challenge-api.js index b6072a4..3dec0e0 100644 --- a/challenge-api-v5-mock/mock-challenge-api.js +++ b/challenge-api-v5-mock/mock-challenge-api.js @@ -5,39 +5,36 @@ const http = require('http') const send = require('http-json-response') const _ = require('lodash') - // Sample challenge const sampleChallenge = { - "id": "96059e8d-4761-4978-9a14-c86ae6b971c3", - "legacyId": 30049360, - "type": "Code", - "track": "Develop", - "name": "Test Challenge 1", - "description": "Test Challenge 1 - Description", - "challengeSettings": [ + 'id': '96059e8d-4761-4978-9a14-c86ae6b971c3', + 'legacyId': 30049360, + 'type': 'Code', + 'track': 'Develop', + 'name': 'Test Challenge 1', + 'description': 'Test Challenge 1 - Description', + 'challengeSettings': [ { - "type": "setting1", - "value": "value1" + 'type': 'setting1', + 'value': 'value1' } ], - "created": "2019-03-02T14:35:53.948Z", - "createdBy": "Copilot1", - "updated": "2019-03-02T14:35:53.948Z", - "updatedBy": "Copilot1" + 'created': '2019-03-02T14:35:53.948Z', + 'createdBy': 'Copilot1', + 'updated': '2019-03-02T14:35:53.948Z', + 'updatedBy': 'Copilot1' } - const responses = { '/v5/challenges/96059e8d-4761-4978-9a14-c86ae6b971c3': sampleChallenge } const mockChallengeV5Api = http.createServer((req, res) => { - -if (req.method === 'GET' && _.includes(Object.keys(responses), req.url)) { + if (req.method === 'GET' && _.includes(Object.keys(responses), req.url)) { return send(res, 200, responses[req.url]) } else { // 404 for other routes - return send(res, 404, {message : 'Challenge not found'}) + return send(res, 404, {message: 'Challenge not found'}) } }) diff --git a/config/default.js b/config/default.js index 008d042..0ae4515 100755 --- a/config/default.js +++ b/config/default.js @@ -22,17 +22,38 @@ module.exports = { SUBMITTER_ROLE_ID: process.env.SUBMITTER_ROLE_ID || '732339e7-8e30-49d7-9198-cccf9451e221', + IS_CREATE_FORUM: process.env.IS_CREATE_FORUM || true, + CREATE_CHALLENGE_RESOURCE_TOPIC: process.env.CREATE_CHALLENGE_RESOURCE_TOPIC || 'challenge.action.resource.create', DELETE_CHALLENGE_RESOURCE_TOPIC: process.env.DELETE_CHALLENGE_RESOURCE_TOPIC || 'challenge.action.resource.delete', + CHALLENGE_ORIGINATOR: process.env.CHALLENGE_ORIGINATOR || 'app.challenge.service', + CHALLENGE_USER_UNREGISTRATION_TOPIC: process.env.CHALLENGE_USER_UNREGISTRATION_TOPIC || 'notifications.kafka.queue.java.test', + CHALLENGE_USER_REGISTRATION_TOPIC: process.env.CHALLENGE_USER_REGISTRATION_TOPIC || 'notifications.kafka.queue.java.test', + CHALLENGE_API_V4_URL: process.env.CHALLENGE_API_V4_URL || 'https://api.topcoder-dev.com/v4/challenges', CHALLENGE_API_V5_URL: process.env.CHALLENGE_API_V5_URL || 'http://localhost:3001/v5/challenges', - RESOURCE_ROLE_API_URL: process.env.RESOURCE_ROLE_API_URL || 'https://api.topcoder-dev.com/v5/resource-roles', AUTH0_URL: process.env.AUTH0_URL || 'https://topcoder-dev.auth0.com/oauth/token', // Auth0 credentials for M2M token AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE || 'https://m2m.topcoder-dev.com/', AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID || 'e6oZAxnoFvjdRtjJs1Jt3tquLnNSTs0e', AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET || 'invalid', AUTH0_PROXY_SERVER_URL: process.env.AUTH0_PROXY_SERVER_URL || 'https://topcoder-dev.auth0.com/oauth/token', - TOKEN_CACHE_TIME: 90 + TOKEN_CACHE_TIME: 90, + + INFORMIX: { + SERVER: process.env.INFORMIX_SERVER || 'informixoltp_tcp', // informix server + DATABASE: process.env.INFORMIX_DATABASE || 'tcs_catalog', // informix database + HOST: process.env.INFORMIX_HOST || 'localhost', // host + PROTOCOL: process.env.INFORMIX_PROTOCOL || 'onsoctcp', + PORT: process.env.INFORMIX_PORT || '2021', // port + DB_LOCALE: process.env.INFORMIX_DB_LOCALE || 'en_US.57372', + USER: process.env.INFORMIX_USER || 'informix', // user + PASSWORD: process.env.INFORMIX_PASSWORD || '1nf0rm1x', // password + POOL_MAX_SIZE: parseInt(process.env.MAXPOOL, 10) || 60, + maxsize: parseInt(process.env.MAXSIZE) || 0, + minpool: parseInt(process.env.MINPOOL, 10) || 1, + idleTimeout: parseInt(process.env.IDLETIMEOUT, 10) || 3600, + timeout: parseInt(process.env.TIMEOUT, 10) || 30000 + } } diff --git a/docker/Dockerfile b/docker/Dockerfile old mode 100755 new mode 100644 index 343ec42..3f05ee5 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,12 +1,49 @@ -# Use the base image with Node.js 10.15-jessie -FROM node:10.15-jessie +FROM ibmcom/informix-innovator-c:12.10.FC12W1IE -# Copy the current directory into the Docker image -COPY . /legacy-resources-processor +ARG servername=informix -# Set working directory for future use -WORKDIR /legacy-resources-processor +USER root +RUN mkdir /app +WORKDIR /home/informix -# Install the dependencies from package.json -RUN npm install -CMD npm run start +RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak && \ + echo "deb http://ftp.debian.org/debian/ stretch main non-free contrib" >/etc/apt/sources.list && \ + echo "deb http://security.debian.org/ stretch/updates main contrib non-free" >>/etc/apt/sources.list + +RUN apt-get -qq update && \ + apt-get -qq install -y wget gcc-6 g++-6 make xz-utils python2.7 git curl + +RUN wget -q -O node10.tar.xz https://nodejs.org/dist/v10.15.1/node-v10.15.1-linux-x64.tar.xz \ + && tar xfJ node10.tar.xz && rm -rf node10.tar.xz + +ENV SERVERNAME=$servername + +COPY docker/esql /opt/ibm/informix/bin/ + +RUN chmod +x /opt/ibm/informix/bin/esql +RUN echo "informixoltp_tcp onsoctcp $SERVERNAME sqlexec" \ + > /opt/ibm/informix/etc/sqlhosts.informixoltp_tcp + +ENV INFORMIXDIR /opt/ibm/informix +ENV INFORMIX_HOME /home/informix +ENV INFORMIXSERVER informixoltp_tcp +ENV INFORMIXTERM terminfo +ENV CLIENT_LOCALE=en_US.utf8 +ENV DB_LOCALE=en_US.utf8 +ENV DBDATE Y4MD- +ENV DBDELIMITER "|" +ENV PATH /home/informix/node-v10.15.1-linux-x64/bin:${INFORMIXDIR}/bin:${INFORMIXDIR}/lib:${INFORMIXDIR}/lib/esql:${PATH} +ENV LD_LIBRARY_PATH ${INFORMIXDIR}/lib:${INFORMIXDIR}/lib/esql:${INFORMIXDIR}/lib/cli +ENV INFORMIXSQLHOSTS /opt/ibm/informix/etc/sqlhosts.informixoltp_tcp +ENV USER root +ENV LICENSE accept + +RUN ln -s /usr/bin/python2.7 /usr/bin/python +RUN echo "sqlexec 2021/tcp" >> /etc/services + +COPY . /app + +WORKDIR /app +RUN rm -rf node_modules && npm install --unsafe-perm + +ENTRYPOINT [ "npm", "start" ] \ No newline at end of file diff --git a/docker/esql b/docker/esql new file mode 100755 index 0000000..cc9f58d --- /dev/null +++ b/docker/esql @@ -0,0 +1,946 @@ +#!/bin/sh +: ' +: ************************************************************************ +: +: +: Licensed Material - Property Of IBM +: +: "Restricted Materials of IBM" +: +: IBM Informix Client SDK +: +: Copyright IBM Corporation 2006, 2014. All rights reserved. +: +: +: Title: esql.sh +: Description: Source for shell script for running the ESQL/C preprocessor +: +: ************************************************************************* +: ' +INFDIR=${INFORMIXDIR=/usr/informix} +PREPCC=${INFDIR}/lib/esql/esqlc +CC="${INFORMIXC=cc} " +CC_TH="${INFORMIXC=gcc} " +CPP="${INFORMIXCPP=g++} " +CPP_TH="${INFORMIXCPP=g++} " +STATICFLAGS="" + +CP=${INFORMIXCP="$CC -E -C"} # cpp which runs before esqlc +PROTECT=${INFDIR}/lib/esql/eprotect # make SQL keyword protection +UPROTECT="${INFDIR}/lib/esql/eprotect -u" # undo SQL keyword protection +UNAMEM=`uname -m` + +CC_AMD32="" +LDF="-Wl,--no-as-needed" + +Usage() +{ + echo ' +Usage: esql [-e] [-thread] [-glu] [esqlcargs] [-cc] [otherargs] [-o outfile] + [-cp] [-onlycp] [-np] [-nup] + [-libs] esqlfile.ec [othersrc.c...] [otherobj.o...] [-lyourlib...] + ' + infxmsg 33490 -e + infxmsg 33491 -thread + infxmsg 33492 -glu + infxmsg 33494 -esqlcargs + infxmsg 33495 -cc + infxmsg 33496 otherargs + infxmsg 33497 -o + infxmsg 33498 -libs + infxmsg 33499 -cp + infxmsg 38700 -onlycp + infxmsg 38701 -np + infxmsg 38702 -nup + +} + + + +if [ x$UNAMEM = "xx86_64" ] +then + CC="$CC $CC_AMD32" + CPP="$CPP $CC_AMD32" +else + CC=$CC + CPP=$CPP +fi + + +: ' +: INFORMIX Global Language Support extensions +: +: Please set an appropriate value to the CC8BITLEVEL shell variable. +: See guidelines below for specifying appropriate CC8BITLEVEL values. +: ' + +MBFILTER=${INFDIR}/lib/esql/esqlmf +CC8BITLEVEL=${CC8BITLEVEL-0} + +: ' +: ---------------------------------------------------------------------------- +: CC8BITLEVEL Remarks +: ---------------------------------------------------------------------------- +: +: 0 C compiler does not allow 8th bit set bytes in literal +: strings and comments. +: +: 1 C compiler does not allow 8th bit set bytes in literal +: strings. +: +: 2 C compiler allows 8th bit set bytes in literal strings; it +: expects all bytes in a multiple-byte character to have the +: 8th bit set. The compiler complains about literal strings if +: any byte of a multiple-byte character is not 8th bit set. +: +: Compilers on Japanese EUC codeset machines usually exhibit +: this behavior. +: +: For example, the compiler will not allow literal multiple +: byte characters with the bytes (\), ("), and (%). +: +: 3 C compiler allows 8th bit set bytes in literal strings; it +: does not expect all bytes in a multiple-byte character to +: have the 8th bit set. +: +: Compilers on Japanese Shift-JIS codeset machines exhibit this +: behavior. This also applies to Chinese Shift-Big5 codeset +: C compilers. +: +: For example, the compiler will accept literal multiple byte +: characters with bytes (\), ("), or (%) as the non-zeroth +: byte of a multibyte character. +: +: ---------------------------------------------------------------------------- +: CC8BITLEVEL Causes $MBFILTER to be invoked as follows: +: ---------------------------------------------------------------------------- +: +: 0 $MBFILTER -all -comm +: 1 $MBFILTER -all +: 2 $MBFILTER +: 3 -- $MBFILTER is not executed -- +: +: ---------------------------------------------------------------------------- +: +: End of INFORMIX Global Language Support extensions +: +: ' + +STAT=0 +TYPE=unix +A= +AO= +B= +BO= +CA= +PA= +PREONLY= +NOLINK= +INCLUDE= +THREAD= +THREAD_SUB= +STATOPT= +TLIB= +THRLIB= +THLIB_SUB= +CPFIRSTFILE=FALSE +CPONLY=FALSE +CPPOPTS= +ETARGET= +STAT=0 +CFILE=FALSE +CCOPT= +CPF_DEF= +## Check for static option and set STATOPT if option is set + +for pass in $* +do + case $pass in + -cc ) + CCOPT=TRUE + ;; + -glu ) + if [ "x$CCOPT" = "x" ] + then + GLUOPT=TRUE + GL_USEGLU=1; export GL_USEGLU + fi + ;; + -thread ) + if [ "x$CCOPT" = "x" ] + then + THREAD_SUB="-thread" + fi + ;; + -static ) + if [ "x$CCOPT" = "x" ] + then + STATOPT="TRUE" + fi + ;; + * ) + ;; + esac +done + +CCOPT= + +# If libraries are added or removed make sure to update -libs option +if [ "x$STATOPT" != "x" ] +then + SLIB=${INFDIR}/lib/esql/libifsql.a + GLIB=${INFDIR}/lib/esql/libifgen.a + OLIB=${INFDIR}/lib/esql/libifos.a + ALIB=${INFDIR}/lib/libifasf.a + GLSLIB=${INFDIR}/lib/esql/libifgls.a + GLXLIB=${INFDIR}/lib/esql/libifglx.a + COMB="$OLIB $GLIB" + NETSTUB=${INFDIR}/lib/netstub.a +else + SLIB=-lifsql + GLIB=-lifgen + OLIB=-lifos + ALIB=-lifasf + GLSLIB=-lifgls + GLXLIB=-lifglx + NETSTUB="-lnetstub " +fi + +SYSLIB="-lm -ldl -lcrypt" +SYSNLIB="-lpthread" +TLILIB=" " +THLIB=$THREADLIB +CHKAPI=${INFDIR}/lib/esql/checkapi.o +LPATH=${LPATH=${INFDIR}/lib} + +nettli="FALSE" +# check for the existence of libraries specified in TLILIB +if test "x$TLILIB" != "x " -a "x$TLILIB" != "x" +then + for lib in $TLILIB + do + if test -f $lib + then + nettli="TRUE" + break + else + continue + fi + done + # Update SYSNLIB if nettli is false + if test "$nettli" = "FALSE" + then + remove=`echo $lib | sed -e s:\/.*\/lib:-l: -e s:[.].*$::` + SYSNLIB=`echo $SYSNLIB | sed -e s:"$remove":: ` + fi +else + nettli="" +fi + +# check if -thread option is used, then dynamic binding to be used by making +# sure that -thread option is applied before the .ec file is processed. + +case $# in +0) + Usage + echo "" + $PREPCC + exit 1 +esac + +ONLYCFILES= + +while true +do + case $1 in + *.c ) + A="$A $1" + BASE="`basename $1 .c`" + AO="$AO $BASE.o" + ONLYCFILES="$ONLYCFILES $1" + CFILE="TRUE" + shift + ;; + *.C ) + B="$B $1" + BASE="`basename $1 .C`" + BO="$BO $BASE.o" + shift + ;; + *.cpp ) + B="$B $1" + BASE="`basename $1 .cpp`" + BO="$BO $BASE.o" + shift + ;; + *.cxx ) + B="$B $1" + BASE="`basename $1 .cxx`" + BO="$BO $BASE.o" + shift + ;; + -cc ) + CCOPT=TRUE + shift + ;; + -shared ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + fi + shift + ;; + -static ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + else + STATOPT="TRUE" + if [ "x$THREAD" != "x" ] + then + SLIB=${INFDIR}/lib/esql/libthsql.a + GLIB=${INFDIR}/lib/esql/libthgen.a + OLIB=${INFDIR}/lib/esql/libthos.a + ALIB=${INFDIR}/lib/libthasf.a + GLSLIB=${INFDIR}/lib/esql/libifgls.a + GLXLIB=${INFDIR}/lib/esql/libifglx.a + COMB="$OLIB $GLIB" + NETSTUB=${INFDIR}/lib/netstub.a + if [ "x$THLIB" = "xDCE" -o "x$THLIB" = "xdce" ] + then + TLIB="-ldce" + else + if [ "x$THLIB" = "xSOL" -o "x$THLIB" = "xsol" ] + then + TLIB="" + else + if [ "x$THLIB" = "xPOSIX" -o "x$THLIB" = "xposix" ] + then + TLIB="-lpthread" + else + if [ "x$THLIB" = "xdynamic" -o "x$THLIB" = "xDYNAMIC" ] + then + TLIB="" + else + infxmsg -33413 + exit 1 + fi + fi + fi + fi + if [ "x$THLIB" != "x" ] + then + THRLIB="$TLIB " + fi + else + SLIB=${INFDIR}/lib/esql/libifsql.a + GLIB=${INFDIR}/lib/esql/libifgen.a + OLIB=${INFDIR}/lib/esql/libifos.a + ALIB=${INFDIR}/lib/libifasf.a + GLSLIB=${INFDIR}/lib/esql/libifgls.a + GLXLIB=${INFDIR}/lib/esql/libifglx.a + COMB="$OLIB $GLIB" + NETSTUB=${INFDIR}/lib/netstub.a + fi + fi + shift + ;; + -thread) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + else + SYSTHRLIB="-lm -ldl -lcrypt" + SYSTHRNLIB="" + if [ "x$STATOPT" != "x" ] + then + SLIB=${INFDIR}/lib/esql/libthsql.a + GLIB=${INFDIR}/lib/esql/libthgen.a + OLIB=${INFDIR}/lib/esql/libthos.a + ALIB=${INFDIR}/lib/libthasf.a + GLSLIB=${INFDIR}/lib/esql/libifgls.a + GLXLIB=${INFDIR}/lib/esql/libifglx.a + COMB="$OLIB $GLIB" + NETSTUB=${INFDIR}/lib/netstub.a + else + SLIB=-lthsql + GLIB=-lthgen + OLIB=-lthos + ALIB=-lthasf + GLSLIB=-lifgls + GLXLIB=-lifglx + fi + PA="$PA $1" + THREAD=TRUE + if [ "x$THLIB" = "xDCE" -o "x$THLIB" = "xdce" ] + then + TLIB="-ldce" + THLIB_SUB=dce + INCLUDE="$INCLUDE -I/usr/include/dce" + else + if [ "x$THLIB" = "xSOL" -o "x$THLIB" = "xsol" ] + then + TLIB="" + THLIB_SUB=thread + else + if [ "x$THLIB" = "xPOSIX" -o "x$THLIB" = "xposix" ] + then + TLIB="-lpthread" + THLIB_SUB="pthread" + else + if [ "x$THLIB" = "xdynamic" -o "x$THLIB" = "xDYNAMIC" ] + then + TLIB="" + THLIB_SUB="" + else + infxmsg -33413 + exit 1 + fi + fi + fi + fi + THRLIB="$TLIB " + if [ "x$THLIB" != "x" -a "x$THLIB_SUB" != "x" ] + then + if [ ! -f /usr/lib/x86_64-linux-gnu/lib$THLIB_SUB.so ] + then + infxmsg -33413 + exit 1 + fi + fi + CC="$CC_TH -DIFX_THREAD -D_REENTRANT" + CPP="$CPP_TH -DIFX_THREAD -D_REENTRANT" + SYSLIB="$SYSTHRLIB" + SYSNLIB="$SYSNLIB" + fi + shift + ;; + *.ec ) + FNAME=$1 + ECFILE=TRUE + X="`basename $1 .ec`" + Y="$X.c" + ONLYCFILES="$ONLYCFILES $Y" + A="$A $Y" + AO="$AO $X.o" + shift + ;; + *.ecpp ) + FNAME=$1 + ECPPFILE=TRUE + X="`basename $1 .ecpp`" + Y="$X.C" + B="$B $Y" + BO="$BO $X.o" + shift + ;; + -o ) + if [ "x$2" = "x" ] + then + infxmsg -33414 + exit 1 + fi + + case $2 in + *.ec) + infxmsg -33415 $2 + exit 1 + esac + + # + # Accumulate the -o options in a new variable called + # ETARGET. If the user has set GL_USEGLU, will need just the + # filenames if "-c" option is not given. Later on, ETARGET + # is merged in with the variable A + # + # Same with CPPOPTS. + # + # No change in behaviour, if GL_USEGLU is not set. + # + if [ x$GL_USEGLU = "x1" ] + then + ETARGET="$ETARGET $1" + else + A="$A $1" + CPPOPTS="$CPPOPTS $1" + fi + CA="$CA $1" + shift + if [ x$GL_USEGLU = "x1" ] + then + ETARGET="$ETARGET $1" + else + A="$A $1" + CPPOPTS="$CPPOPTS $1" + fi + CA="$CA $1" + shift + ;; + -e ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + else + PREONLY=1; + fi + shift + ;; + -c ) + NOLINK=1; + A="$A $1" + shift + ;; + -I* ) + if [ "x$CCOPT" = "x" ] + then + PA="$PA $1" + fi + INCLUDE="$INCLUDE $1" + shift + ;; + -V ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + shift + else + $PREPCC $1 + STAT=$? + exit $STAT + fi + ;; + -version ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + shift + else + $PREPCC $1 + STAT=$? + exit $STAT + fi + ;; + -T ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1 $2" + CA="$CA $1 $2" + CPPOPTS="$CPPOPTS $1 $2" + else + TYPE="$2" + fi + shift + shift + ;; + -icheck ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + else + PA="$PA $1" + fi + shift + ;; + -P ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + else + PA="$PA $1" + PREONLY=1; + fi + shift + ;; + -g ) + CC="$CC -g" + CPP="$CPP -g" + PA="$PA $1" + shift + ;; + -G ) + CC="$CC -g" + CPP="$CPP -g" + PA="$PA $1" + shift + ;; + -nln ) + CC="$CC -g" + CPP="$CPP -g" + PA="$PA $1" + shift + ;; + -ED* ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + else + PA="$PA $1" + fi + shift + ;; + -EU* ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + else + PA="$PA $1" + fi + shift + ;; + -ansi ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + else + PA="$PA $1" + fi + shift + ;; + -keepccomment ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + else + PA="$PA $1" + fi + shift + ;; + -xopen ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + else + PA="$PA $1" + fi + shift + ;; + -nowarn ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + else + PA="$PA $1" + fi + shift + ;; + -local ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + else + PA="$PA $1" + fi + shift + ;; + -log ) + if test $# -lt 2 + then +# esql: file name required with -log + infxmsg -33412 + exit 1 + else + if [ "x$CCOPT" != "x" ] + then + A="$A $1 $2" + CA="$CA $1 $2" + CPPOPTS="$CPPOPTS $1 $2" + else + PA="$PA $1 $2" + fi + shift 2 + fi + ;; + -libs ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + shift + else + # missing TLILIBS, add the stub library + if [ "$nettli" = "FALSE" ] + then + SYSNLIB="$SYSNLIB $NETSTUB " + fi + if test "$STATOPT" + then + for library in $A $B $COMB $SLIB $ALIB $COMB $GLSLIB $SYSNLIB $SYSLIB $GLXLIB $THRLIB + do + echo $library + done + else + for library in $A $B $SLIB $ALIB $GLIB $OLIB $GLSLIB $SYSNLIB $SYSLIB $CHKAPI $GLXLIB $THRLIB + do + echo $library + done + fi + STAT=$? + exit $STAT + fi + ;; + + -onlycp ) # run only C preprocessor + CPFIRST=TRUE; + CPONLY=TRUE; + PREONLY=1; + shift + ;; + -cp ) # run C preprocessor before esqlc + CPFIRST=TRUE; + shift + ;; + -np ) # noprotect + PROTECTOPT=-n + UPROTECTOPT=-n + shift + ;; + -nup ) # nounprotect + UPROTECTOPT=-n + CPFIRST=TRUE; + CPONLY=TRUE; + PREONLY=1 + shift + ;; + *.ecp ) # run C preprocessor before esqlc + FNAME=$1 + ECFILE=TRUE + Y="`basename $1 .ecp`".c + A="$A $Y" + ONLYCFILES="$ONLYCFILES $Y" + Y="`basename $1 .ecp`" + AO="$AO $Y.o" + CPFIRSTFILE=TRUE; + shift + ;; + -glu ) + if [ "x$CCOPT" != "x" ] + then + A="$A $1" + CA="$CA $1" + CPPOPTS="$CPPOPTS $1" + fi + shift + ;; + + -* ) # collect the options + A="$A $1" + CA="$CA $1" + if [ "x$GL_USEGLU" = "x1" -a "x$CCOPT" = "xTRUE" ] + then + CPPOPTS="$CPPOPTS" + else + CPPOPTS="$CPPOPTS $1" + fi + shift + ;; + + "" ) + break; + ;; + * ) + A="$A $1" + CPPOPTS="$CPPOPTS $1" + shift + ;; + esac + +# preprocess .ec, .ecp or .ecpp files + + if test "$ECFILE" -o "$ECPPFILE" + then + if [ "$CPFIRST" = "TRUE" -o "$CPFIRSTFILE" = "TRUE" ]; then + base=`basename $FNAME .ecp` + base=`basename $base .ec` + + if [ "$PROTECTOPT" = "-n" ]; then + cpinput=$base.pcp + cp $FNAME $cpinput + cpoutput=$base.icp + else #make protected file for C-preprocessor + cpinput=$base.pcp + if [ "$UPROTECTOPT" = "-n" ]; then + cpoutput=$base.icp + else + cpoutput=$base.tmp + fi + CPF_DEF=-CP + $PROTECT $PROTECTOPT $FNAME >$cpinput + fi + + $CP $INCLUDE -I$INFDIR/incl/esql $CA $cpinput > $cpoutput + STAT=$? + + if [ "$UPROTECTOPT" != "-n" ]; then + $UPROTECT $UPROTECTOPT < $cpoutput > $base.icp + fi + + if [ "$STAT" = 0 ]; then + rm -f $base.tmp $base.pcp + fi + + FNAME=$base.icp + CPFIRSTFILE=FALSE + fi + if [ "$STAT" = 0 -a "$CPONLY" != "TRUE" ]; then + if [ "x$THREAD_SUB" != "x" ] + then + $PREPCC $THREAD_SUB $PA $CPF_DEF -t $TYPE $FNAME + STAT=$? + else + $PREPCC $PA $CPF_DEF -t $TYPE $FNAME + STAT=$? + fi + CFILE="TRUE" + fi + if [ "$STAT" = 0 -a "$CPONLY" != "TRUE" ]; then + rm -f $base.icp + fi + + if test "$STAT" != "0" + then + exit $STAT + fi + ECFILE= + ECPPFILE= + fi +done + +if test "$PREONLY" +then + exit $STAT +fi + +# missing TLILIBS, add the stub library +if [ "$nettli" = "FALSE" ] +then + SYSNLIB="$SYSNLIB $NETSTUB " +fi + +: ' +: INFORMIX Global Language Support extensions +: +: Preprocess multibyte characters in .c files before invoking C compiler +: ' + +if [ "X$CC8BITLEVEL" != "X3" -a "X$ESQLMF" = "X1" ] ; then + if [ X$CC8BITLEVEL = X0 ] ; then + $MBFILTER -all -comm $A $B + elif [ X$CC8BITLEVEL = X1 ] ; then + $MBFILTER -all $A $B + else + $MBFILTER $A $B + fi +fi +: End INFORMIX Global Language Support extensions + +# If the command line includes '-c', no linking happens; zap library options. +# Do not do it earlier (-c -static would overwrite the library options again). +if test "$NOLINK" +then + COMB="" + SLIB="" + ALIB="" + GLSLIB="" + SYSNLIB="" + SYSLIB="" + GLXLIB="" + THRLIB="" + CHKAPI="" + LIBPATHS="" + GLIB="" + OLIB="" + LDF="" +else + LIBPATHS="-L$LPATH -L$LPATH/esql" + # + # If GL_USEGLU is set to 1, then compile the .C files + # with the C compiler and set the compiler to C++. This is so that + # the linking phase will use C++. To avoid further changes to the + # linker line, simple set the variable A to AO. + # + if [ x$GL_USEGLU = "x1" ] + then + if [ "x`which $INFORMIXCPP 2> /dev/null`" = "x" ] + then + if [ "x$GLUOPT" != "x" ] + then + infxmsg -33488 + else + infxmsg -33489 + fi + exit 1 + fi + if test "$ONLYCFILES" -a "X$CFILE" = "XTRUE" + then + $CC -I$INFDIR/incl/esql $INCLUDE $CPF_DEF -c $ONLYCFILES + STAT=$? + fi + # + # Set the linker to CPP + # Set the source files to objects + # + CC=$CPP + A="$AO $CPPOPTS" + fi +fi + +# +# Put back the -o options removed from A and CPPOPTS, if +# GL_USEGLU is set. +# +if [ x$GL_USEGLU = "x1" ] +then + A="$A $ETARGET" + STATICFLAGS="$STATICFLAGS " +fi + +# compile .C (c++) files +if test "$B" +then + $CPP -I$INFDIR/incl/esql $INCLUDE $CPPOPTS -c $B + STAT=`expr $STAT + $?` +fi + +if [ x$STAT = x0 ] +then + # compile .c files, link with c++ .o's in $BO + if test "$STATOPT" + then + exec $CC -I$INFDIR/incl/esql $INCLUDE $STATICFLAGS $A $BO $COMB $SLIB $ALIB $COMB $SLIB $GLSLIB $GLXLIB $SYSNLIB $THRLIB $SYSLIB + else + exec $CC -I$INFDIR/incl/esql $INCLUDE $LIBPATHS $A $CHKAPI $BO $LDF $SLIB $ALIB $GLIB $OLIB $GLSLIB $GLXLIB $SYSNLIB $THRLIB $SYSLIB + fi + STAT=$? +fi +exit $STAT diff --git a/package.json b/package.json index 916e3c0..db23af5 100755 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "config": "^3.0.1", "get-parameter-names": "^0.3.0", "http-status-codes": "^1.3.2", + "ifxnjs": "^10.0.5", "joi": "^14.3.1", "lodash": "^4.17.19", "no-kafka": "^3.4.3", diff --git a/src/client/EsFeederServiceClient.js b/src/client/EsFeederServiceClient.js new file mode 100644 index 0000000..06f4c26 --- /dev/null +++ b/src/client/EsFeederServiceClient.js @@ -0,0 +1,14 @@ + +/** + * Nofity challenge change to es feeder service + * + * @param challengeId challenge id + */ +async function notifyChallengeChange (challengeId) { + // TODO: notify challenge change to ES + console.log('Notify challenge change to ES') +} + +module.exports = { + notifyChallengeChange +} diff --git a/src/common/helper.js b/src/common/helper.js index b840ad7..6e75cd3 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -2,17 +2,42 @@ * Contains generic helper methods */ +global.Promise = require('bluebird') const _ = require('lodash') const config = require('config') +const ifxnjs = require('ifxnjs') +const logger = require('./logger') +const util = require('util') const request = require('superagent') const busApi = require('topcoder-bus-api-wrapper') const m2mAuth = require('tc-core-library-js').auth.m2m const m2m = m2mAuth(_.pick(config, ['AUTH0_URL', 'AUTH0_AUDIENCE', 'TOKEN_CACHE_TIME', 'AUTH0_PROXY_SERVER_URL'])) +const Pool = ifxnjs.Pool +const pool = Promise.promisifyAll(new Pool()) +pool.setMaxPoolSize(config.get('INFORMIX.POOL_MAX_SIZE')) // Bus API Client let busApiClient +/** + * Get Informix connection using the configured parameters + * @return {Object} Informix connection + */ +async function getInformixConnection () { + // construct the connection string from the configuration parameters. + const connectionString = 'SERVER=' + config.get('INFORMIX.SERVER') + + ';DATABASE=' + config.get('INFORMIX.DATABASE') + + ';HOST=' + config.get('INFORMIX.HOST') + + ';Protocol=' + config.get('INFORMIX.PROTOCOL') + + ';SERVICE=' + config.get('INFORMIX.PORT') + + ';DB_LOCALE=' + config.get('INFORMIX.DB_LOCALE') + + ';UID=' + config.get('INFORMIX.USER') + + ';PWD=' + config.get('INFORMIX.PASSWORD') + const conn = await pool.openAsync(connectionString) + return Promise.promisifyAll(conn) +} + /** * Get M2M token * @return {String} m2m token @@ -99,10 +124,74 @@ async function postBusEvent (topic, payload) { }) } +/** + * Prepare Informix statement + * @param {Object} connection the Informix connection + * @param {String} sql the sql + * @return {Object} Informix statement + */ +async function prepare (connection, sql) { + // logger.debug(`Preparing SQL ${sql}`) + const stmt = await connection.prepareAsync(sql) + return Promise.promisifyAll(stmt) +} + +async function ESFeederServiceClient () { +} + +/** + * Query data from database + * @param sql + * @param params + * @returns {Promise} + */ +async function queryDataFromDB (sql, params) { + let result = null + const connection = await getInformixConnection() + try { + await connection.beginTransactionAsync() + result = await connection.queryAsync(util.format(sql, ...params)) + await connection.commitTransactionAsync() + } catch (e) { + logger.error(`Error in 'queryDataFromDB' ${e}, rolling back transaction`) + await connection.rollbackTransactionAsync() + throw e + } finally { + await connection.closeAsync() + } + return result +} + +/** + * Execute sql on database + * @param sql + * @param params + */ +async function executeSQLonDB (sql, params) { + const connection = await getInformixConnection() + try { + await connection.beginTransactionAsync() + const query = await prepare(connection, sql) + await query.executeAsync(params) + await connection.commitTransactionAsync() + } catch (e) { + logger.error(`Error in 'executeSQLonDB' ${e}, rolling back transaction`) + await connection.rollbackTransactionAsync() + throw e + } finally { + await connection.closeAsync() + } +} + module.exports = { + getInformixConnection, getM2Mtoken, getRequest, postRequest, deleteRequest, - postBusEvent + postBusEvent, + prepare, + ESFeederServiceClient, + queryDataFromDB, + executeSQLonDB } diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..bd33ed5 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,25 @@ +const SUBMITTER_RESOURCE_ROLE_ID = 1 +const PROJECT_USER_AUDIT_CREATE_TYPE = 1 +const DESIGN_PROJECT_TYPE = 1 +const DEVELOPMENT_PROJECT_TYPE = 2 +const PROJECT_USER_AUDIT_DELETE_TYPE = 2 +const COMPONENT_TESTING_PROJECT_TYPE = 5 +const APPEALS_COMPLETE_EARLY_PROPERTY_ID = 13 +const TIMELINE_NOTIFICATION_ID = 1 +const COPILOT_POSTING_PROJECT_TYPE = 29 +const NO_VALUE = 'NO' +const RESOURCE_ROLE_OBSERVER_ID = 12 + +module.exports = { + SUBMITTER_RESOURCE_ROLE_ID, + PROJECT_USER_AUDIT_CREATE_TYPE, + DESIGN_PROJECT_TYPE, + DEVELOPMENT_PROJECT_TYPE, + PROJECT_USER_AUDIT_DELETE_TYPE, + COMPONENT_TESTING_PROJECT_TYPE, + APPEALS_COMPLETE_EARLY_PROPERTY_ID, + TIMELINE_NOTIFICATION_ID, + COPILOT_POSTING_PROJECT_TYPE, + RESOURCE_ROLE_OBSERVER_ID, + NO_VALUE +} diff --git a/src/dao/ForumWrapper.js b/src/dao/ForumWrapper.js new file mode 100644 index 0000000..78321da --- /dev/null +++ b/src/dao/ForumWrapper.js @@ -0,0 +1,63 @@ + +/** + * Assign user role + * + * @param userId the user id + * @param groupName the group name + */ +async function assignRole (userId, groupName) { + // TODO: assign user role from group + console.log(`Need assign user ${userId} to group ${groupName}`) +} + +/** + * Remove user role + * + * @param userId the user id + * @param groupName the group name + */ +async function removeRole (userId, groupName) { + // TODO: remove user role from group + console.log(`Need remove user ${userId} from group ${groupName}`) +} + +/** + * Remove user permission from forum category + * + * @param userId the user id + * @param forumCategoryId the forum category id + */ +async function removeUserPermission (userId, forumCategoryId) { + // TODO: need remove user permission from group + console.log(`Need remove user ${userId} permission from forum category ${forumCategoryId}`) +} + +/** + * Create category watch + * + * @param userId the user id + * @param forumCategoryId the forum category id + */ +async function createCategoryWatch (userId, forumCategoryId) { +// TODO: need create category watch for user + console.log(`Need create category watch for user ${userId} to forum category ${forumCategoryId}`) +} + +/** + * Delete category watch + * + * @param userId the user id + * @param forumCategoryId the forum category id + */ +async function deleteCategoryWatch (userId, forumCategoryId) { +// TODO: need delete category watch for user + console.log(`Need delete category watch for user ${userId} from forum category ${forumCategoryId}`) +} + +module.exports = { + assignRole, + removeRole, + removeUserPermission, + createCategoryWatch, + deleteCategoryWatch +} diff --git a/src/dao/RegistrationDAO.js b/src/dao/RegistrationDAO.js new file mode 100644 index 0000000..ce514c8 --- /dev/null +++ b/src/dao/RegistrationDAO.js @@ -0,0 +1,518 @@ +const helper = require('../common/helper') + +const QUERY_GET_USER_RELIABILITY = ` +SELECT rating +FROM user_reliability +WHERE + user_id = %d AND +phase_id = (SELECT 111 + project_category_id FROM project WHERE project_id = %d) +` + +const QUERY_INSERT_RESOURCE_WITH_ROLE = ` +INSERT INTO resource + ( resource_id, + resource_role_id, + project_phase_id, + project_id, + user_id, + create_user, + create_date, + modify_user, + modify_date) +VALUES + (?, ?, null, ?, ?, ?, CURRENT, ?, CURRENT)` + +async function persistResourceWithRoleId (userId, challengeId, resourceId, roleId) { + await helper.executeSQLonDB(QUERY_INSERT_RESOURCE_WITH_ROLE, [resourceId, roleId, challengeId, userId, userId, userId]) +} + +const QUERY_INSERT_RESOURCE = ` +INSERT INTO resource + ( resource_id, + resource_role_id, + project_phase_id, + project_id, + user_id, + create_user, + create_date, + modify_user, + modify_date) +VALUES + (?, 1, null, ?, ?, ?, CURRENT, ?, CURRENT)` + +async function persistResource (userId, challengeId, resourceId) { + await helper.executeSQLonDB(QUERY_INSERT_RESOURCE, [resourceId, challengeId, userId, userId, userId]) +} + +/** + * Get user reliability + * @param userId the userId to pass + * @param challengeId the challengeId to pass + * @return the result + */ +async function getUserReliability (userId, challengeId) { + return helper.queryDataFromDB(QUERY_GET_USER_RELIABILITY, [userId, challengeId]) +} + +const QUERY_GET_CHALLENGE_NOTIFICATION_COUNT = ` +SELECT count(*) AS total_count FROM notification +WHERE + project_id = %d + AND external_ref_id = %d + AND notification_type_id = %d +` + +/** + * Get challenge notification count + * + * @param challengeId the challengeId to pass + * @param userId the userId to pass + * @param notificationTypeId the notificationTypeId to pass + * @return the result + */ +async function getChallengeNotificationCount (challengeId, userId, notificationTypeId) { + return helper.queryDataFromDB(QUERY_GET_CHALLENGE_NOTIFICATION_COUNT, [challengeId, userId, notificationTypeId]) +} + +const QUERY_INSERT_CHALLENGE_NOTIFICATION = ` +INSERT INTO notification +( + project_id, + external_ref_id, + notification_type_id, + create_user, + create_date, + modify_user, + modify_date +) +VALUES +(%d, %d, %d, %d, CURRENT, %d, CURRENT) +` + +/** + * Insert challenge notification + * @param challengeId the challengeId to pass + * @param userId the userId to pass + * @param notificationTypeId the notificationTypeId to pass + */ +async function insertChallengeNotification (challengeId, userId, notificationTypeId) { + await helper.executeSQLonDB(QUERY_INSERT_CHALLENGE_NOTIFICATION, [challengeId, userId, notificationTypeId, userId, userId]) +} + +const QUERY_GET_ACTIVE_FORUM_CATEGORY = ` +SELECT jive_category_id +FROM comp_versions cv +INNER JOIN comp_catalog cc on cv.component_id = cc.component_id +INNER JOIN comp_jive_category_xref cjcx on cjcx.comp_vers_id = cv.comp_vers_id +where cc.component_id = %d +` + +/** + * Get active forum category + * @param componentId the componentId to pass + * @return the result + */ +async function getActiveForumCategory (componentId) { + return helper.queryDataFromDB(QUERY_GET_ACTIVE_FORUM_CATEGORY, [componentId]) +} + +const QUERY_CHECK_USER_ACTIVATED = 'SELECT status FROM user WHERE user_id = %d' + +/** + * Check user activated + * @param userId the userId to pass + * @return the result + */ +async function checkUserActivated (userId) { + return helper.queryDataFromDB(QUERY_CHECK_USER_ACTIVATED, [userId]) +} + +const QUERY_CHECK_CHALLENGE_EXISTS = ` +SELECT + CASE WHEN (p.project_studio_spec_id is NULL) THEN 0 ELSE 1 END as is_studio +FROM project p +WHERE p.project_id = %d +` + +/** + * Check challenge exists + * @param challengeId the challengeId to pass + * @return the result + */ +async function checkChallengeExists (challengeId) { + return helper.queryDataFromDB(QUERY_CHECK_CHALLENGE_EXISTS, [challengeId]) +} + +const QUERY_GET_CHALLENGE_ACCESSIBILITY_AND_GROUPS = ` +SELECT + ce.is_studio, + sg.challenge_group_ind, + ugx.group_id AS user_group_xref_found, + sg.group_id AS group_id +FROM + ( + ( + contest_eligibility ce + LEFT JOIN group_contest_eligibility gce + ON ce.contest_eligibility_id = gce.contest_eligibility_id + ) + LEFT JOIN security_groups sg + ON gce.group_id = sg.group_id + ) + LEFT JOIN ( + SELECT group_id FROM user_group_xref WHERE login_id = %d + ) ugx + ON ugx.group_id = gce.group_id +WHERE ce.contest_id = %d` + +/** + * Get challenge accessibility and groups + * @param userId the user_id to pass + * @param challengeId the challengeId to pass + * @return the result + */ +async function getChallengeAccessibilityAndGroups (userId, challengeId) { + return helper.queryDataFromDB(QUERY_GET_CHALLENGE_ACCESSIBILITY_AND_GROUPS, [userId, challengeId]) +} + +const QUERY_CHECK_CHALLENGE_IS_COPILOT_POSTING = ` +SELECT (project_id IS NOT NULL) as challenge_is_copilot +FROM project p +WHERE p.project_id = %d +AND p.project_category_id = 29` + +/** + * check the challenge is copilot posting + * @param challengeId the challenge id to check + * @return the result list + */ +async function checkChallengeIsCopilotPosting (challengeId) { + const result = await helper.queryDataFromDB(QUERY_CHECK_CHALLENGE_IS_COPILOT_POSTING, [challengeId]) + if (result && result.length > 0) { + return result.challenge_is_copilot + } + return false +} + +const QUERY_CHECK_IS_COPILOT = ` +select + (copilot_profile_id IS NOT NULL) as user_is_copilot +from copilot_profile where user_id = %d and copilot_profile_status_id = 1` + +/** + * Check user is copilot + * + * @param userId the userId to check + * @return the result list + */ +async function checkIsCopilot (userId) { + const result = await helper.queryDataFromDB(QUERY_CHECK_IS_COPILOT, [userId]) + if (result && result.length > 0) { + return result.user_is_copilot + } + return false +} + +const QUERY_CHALLENGE_REGISTRATION_VALIDATIONS = ` +select + p.project_id as challengeId, + p.project_category_id as projectCategoryId, + (pp_reg_open.project_id IS NOT NULL) as regOpen, + (r.project_id IS NOT NULL) as userRegistered, + (us.user_id IS NOT NULL) as user_suspended, + (uax.user_id IS NOT NULL OR coder.coder_id IS NOT NULL) as userCountryBanned, + (coder2.comp_country_code IS NULL OR coder2.comp_country_code = '') as compCountryIsNull, + (cop.copilot_profile_id IS NOT NULL) as userIsCopilot, + (pctl.name) as copilotType +from project p +left join + project_phase pp_reg_open + on p.project_id = pp_reg_open.project_id + and pp_reg_open.phase_type_id = 1 + and pp_reg_open.phase_status_id = 2 +left join + resource r + on r.project_id = p.project_id and r.resource_role_id = 1 + and user_id = %d +left join + user_status us + on us.user_id = %d + and us.user_status_type_id = 1 + and us.user_status_id = 3 +left outer join ( + informixoltp:user_address_xref uax join ( + informixoltp:address a join informixoltp:country c + on a.country_code=c.country_code + ) + on uax.address_id=a.address_id + and c.country_name in ( "Iran", "North Korea", "Cuba", "Sudan", "Syria" ) +) on uax.user_id = %d +left outer join ( + informixoltp:coder coder join informixoltp:country country + on ( + coder.comp_country_code=country.country_code OR + coder.home_country_code=country.country_code + ) and country.country_name in ( "Iran", "North Korea", "Cuba", "Sudan", "Syria" ) +) on coder.coder_id = %d +left outer join informixoltp:coder coder2 on coder2.coder_id = %d +left join ( + project_copilot_type pct join project_copilot_type_lu pctl + on pct.project_copilot_type_id = pctl.project_copilot_type_id +) on pct.project_id = p.project_id +left join + copilot_profile cop ON cop.user_id = %d and cop.copilot_profile_status_id = 1 +where p.project_id = %d +` +/** + * Validate the challenge registration + * + * @param userId the user id + * @param challengeId the challenge id + * @return the challenge registration validation + */ +async function validateChallengeRegistration (userId, challengeId) { + const result = await helper.queryDataFromDB(QUERY_CHALLENGE_REGISTRATION_VALIDATIONS, [userId, userId, userId, userId, userId, userId, challengeId]) + if (result && result.length > 0) { + return result[0] + } + return result +} + +const QUERY_GET_COMPONENT_INFO = ` +SELECT + d.scheduled_end_time AS initialSubmissionDate, + c.component_id AS componentId, + c.phase_id AS phaseId, + c.comp_vers_id AS componentVersionId, + c.version AS version, + nvl(c.comments, '') AS comments, + (select project_category_id from project where project_id = %d) AS projectCategoryId, + (select value from project_info where project_id = %d AND project_info_type_id = 6) AS projectName, + pi79.value AS reviewType +FROM comp_versions c + , project_info p + , project_phase d + , project_info pi79 +WHERE + p.project_info_type_id = 2 + AND c.phase_id IN (112, 113) + AND c.component_id = p.value + AND p.project_id = %d + AND d.project_id = %d and d.phase_type_id = 2 + AND pi79.project_info_type_id = 79 +AND pi79.project_id = %d` + +/** + * Get component info result + * + * @param challengeId the challengeId to pass + * @return the result + */ +async function getComponentInfo (challengeId) { + return helper.queryDataFromDB(QUERY_GET_COMPONENT_INFO, [challengeId, challengeId, challengeId, challengeId, challengeId]) +} + +const QUERY_GET_USER_RATING = ` +SELECT +nvl((SELECT rating FROM user_rating +WHERE phase_id = %d AND user_id = %d), 0) as rating +FROM dual +` + +/** + * Get user rating + * @param userId the userId to pass + * @param phaseId the phaseId to pass + * @return the rating result + */ +async function getUserRating (userId, phaseId) { + return helper.queryDataFromDB(QUERY_GET_USER_RATING, [phaseId, userId]) +} + +const QUERY_INSERT_REGISTRATION_RECORD = ` +INSERT INTO component_inquiry ( + component_inquiry_id, + component_id, + user_id, + comment, + agreed_to_terms, + rating, + phase, + tc_user_id, + version, + project_id +) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + +/** + * Insert registration record + * + * @param componentInquiryId the componentInquiryId to pass + * @param componentId the componentId to pass + * @param userId the userId to pass + * @param comment the comment to pass + * @param agreedToTerms the agreedToTerms to pass + * @param rating the rating to pass + * @param phase the phase to pass + * @param tcUserId the tcUserId to pass + * @param version the version to pass + * @param challengeId the challengeId to pass + */ +async function insertRegistrationRecord (componentInquiryId, componentId, userId, comment, agreedToTerms, rating, phase, tcUserId, version, challengeId) { + await helper.executeSQLonDB(QUERY_INSERT_REGISTRATION_RECORD, [componentInquiryId, componentId, userId, comment, agreedToTerms, rating, phase, tcUserId, version, challengeId]) +} + +const QUERY_AUDIT_CHALLENGE_REGISTRATION = ` +INSERT INTO project_user_audit +( project_user_audit_id, + project_id, + resource_user_id, + resource_role_id, + audit_action_type_id, + action_date, + action_user_id +) +VALUES +( PROJECT_USER_AUDIT_SEQ.nextval, + ?, + ?, + ?, + ?, + CURRENT, + ?)` + +/** + * Audit challenge registration + * @param challengeId the challengeId to pass + * @param resourceUserId the resourceUserId to pass + * @param resourceRoleId the resourceRoleId to pass + * @param auditActionTypeId the auditActionTypeId to pass + * @param actionUserId the actionUserId to pass + */ +async function auditChallengeRegistration (challengeId, resourceUserId, resourceRoleId, auditActionTypeId, actionUserId) { + await helper.executeSQLonDB(QUERY_AUDIT_CHALLENGE_REGISTRATION, [challengeId, resourceUserId, resourceRoleId, auditActionTypeId, actionUserId]) +} + +const QUERY_INSERT_CHALLENGE_RESULT = ` +INSERT INTO project_result +( project_id, + user_id, + rating_ind, + valid_submission_ind, + old_rating +) +VALUES ( ?, ?, ?, ?, ?)` + +/** + * Insert challenge result + * @param challengeId the challengeId to pass + * @param userId the userId to pass + * @param ratingInd the ratingInd to pass + * @param validSubmissionInd the validSubmissionInd to pass + * @param oldRating the oldRating to pass + */ +async function insertChallengeResult (challengeId, userId, ratingInd, validSubmissionInd, oldRating) { + await helper.executeSQLonDB(QUERY_INSERT_CHALLENGE_RESULT, [challengeId, userId, ratingInd, validSubmissionInd, oldRating]) +} + +const QUERY_INSERT_RESOURCE_INFO = ` +INSERT INTO resource_info +( resource_id, + resource_info_type_id, + value, + create_user, + create_date, + modify_user, + modify_date +) +VALUES +( ?, ?, ?, ?, CURRENT, ?, CURRENT)` + +const QUERY_INSERT_RESOURCE_INFO_FOR_TYPE6 = ` +INSERT INTO resource_info +( resource_id, + resource_info_type_id, + value, + create_user, + create_date, + modify_user, + modify_date +) +VALUES +( ?, ?, CURRENT, ?, CURRENT, ?, CURRENT)` + +/** + * persist the resource info + * @param userId the userId to pass + * @param resourceId the resourceId to pass + * @param resourceInfoTypeId the resourceInfoTypeId to pass + * @param value the value to pass + */ +async function persistResourceInfo (userId, resourceId, resourceInfoTypeId, value) { + if (resourceInfoTypeId === 6) { + await helper.executeSQLonDB(QUERY_INSERT_RESOURCE_INFO_FOR_TYPE6, [resourceId, resourceInfoTypeId, userId, userId]) + } else { + await helper.executeSQLonDB(QUERY_INSERT_RESOURCE_INFO, [resourceId, resourceInfoTypeId, value, userId, userId]) + } +} + +const QUERY_GET_ALL_RESOURCE_ROLES = 'select resource_role_id as id, name from resource_role_lu' +/** + * Get all resource roles + * + * @return a list of resource roles + */ +async function getResourceRoles () { + return helper.queryDataFromDB(QUERY_GET_ALL_RESOURCE_ROLES, []) +} + +const QUERY_GET_CHALLENGE_TERMS_OF_USE = ` +SELECT tou.terms_of_use_id as terms_of_use_id, + tou.title as title, + tou.url as url, + touat.name as agreeability_type, + (utuox.user_id IS NOT NULL) as agreed, + dtx.docusign_template_id +FROM project_role_terms_of_use_xref +INNER JOIN terms_of_use tou ON project_role_terms_of_use_xref.terms_of_use_id = tou.terms_of_use_id +INNER JOIN common_oltp:terms_of_use_agreeability_type_lu touat ON touat.terms_of_use_agreeability_type_id = tou.terms_of_use_agreeability_type_id +LEFT JOIN user_terms_of_use_xref utuox ON utuox.terms_of_use_id = tou.terms_of_use_id AND utuox.user_id = %d +LEFT JOIN common_oltp:terms_of_use_docusign_template_xref dtx ON dtx.terms_of_use_id = project_role_terms_of_use_xref.terms_of_use_id +WHERE project_id = %d AND +resource_role_id = %d +ORDER BY group_ind, sort_order` + +/** + * Get terms of use + * + * @param userId the user id + * @param challengeId the challenge id + * @param roleId the role id + * @return the challenge registration validation + */ +async function getUseTermsOfAgree (userId, challengeId, roleId) { + return helper.queryDataFromDB(QUERY_GET_CHALLENGE_TERMS_OF_USE, [userId, challengeId, roleId]) +} + +module.exports = { + persistResource, + persistResourceWithRoleId, + persistResourceInfo, + getUserReliability, + getChallengeNotificationCount, + insertChallengeNotification, + getActiveForumCategory, + checkUserActivated, + checkChallengeExists, + getChallengeAccessibilityAndGroups, + checkChallengeIsCopilotPosting, + checkIsCopilot, + validateChallengeRegistration, + getComponentInfo, + getUserRating, + insertRegistrationRecord, + auditChallengeRegistration, + insertChallengeResult, + getResourceRoles, + getUseTermsOfAgree +} diff --git a/src/dao/SequenceDAO.js b/src/dao/SequenceDAO.js new file mode 100644 index 0000000..32c9202 --- /dev/null +++ b/src/dao/SequenceDAO.js @@ -0,0 +1,42 @@ +const util = require('util') +const helper = require('../common/helper') +const logger = require('../common/logger') + +const QUERY_GET_COMP_INQUIRY_ID_NEXT = 'SELECT SEQUENCE_COMPONENT_INQUIRY_SEQ.nextval AS nextId FROM systables WHERE tabid = 1' +const QUERY_GET_RESOURCE_ID_NEXT = 'SELECT SEQUENCE_RESOURCE_ID_SEQ.nextval AS nextId FROM systables WHERE tabid = 1' + +// const QUERY_GET_COMP_INQUIRY_ID_NEXT = 'SELECT PROJECT_USER_AUDIT_SEQ.nextval AS nextId FROM systables WHERE tabid = 1' +// const QUERY_GET_RESOURCE_ID_NEXT = 'SELECT PROJECT_USER_AUDIT_SEQ.nextval AS nextId FROM systables WHERE tabid = 1' + +async function getNextIdBySQL (sql) { + let res = null + const connection = await helper.getInformixConnection() + try { + await connection.beginTransactionAsync() + res = await connection.queryAsync(util.format(sql)) + await connection.commitTransactionAsync() + } catch (e) { + logger.error(`Error in 'getNextIdBySQL' ${e}, rolling back transaction`) + await connection.rollbackTransactionAsync() + throw e + } finally { + await connection.closeAsync() + } + if (res && res.length > 0) { + return +res[0].nextid + } + return null +} + +async function getResourceSeqNextId () { + return getNextIdBySQL(QUERY_GET_RESOURCE_ID_NEXT) +} + +async function getCompInQuerySeqNextId () { + return getNextIdBySQL(QUERY_GET_COMP_INQUIRY_ID_NEXT) +} + +module.exports = { + getResourceSeqNextId, + getCompInQuerySeqNextId +} diff --git a/src/dao/UnRegistrationDAO.js b/src/dao/UnRegistrationDAO.js new file mode 100644 index 0000000..d275af3 --- /dev/null +++ b/src/dao/UnRegistrationDAO.js @@ -0,0 +1,235 @@ +const util = require('util') +const helper = require('../common/helper') +const prepare = helper.prepare +const logger = require('../common/logger') + +const QUERY_CHALLENGE_UNREGISTRATION_VALIDATIONS = ` +select distinct + p.project_id as challengeId, + p.project_category_id as challengeCategoryId, + (pp_reg_open.project_id IS NOT NULL) as regOpen, + (select ri.value is not null from resource r, resource_info ri + where r.project_id = p.project_id and r.resource_role_id = 1 + and r.resource_id = ri.resource_id and ri.resource_info_type_Id = 1 and ri.value = %d) as userRegistered, + CASE WHEN (p.project_studio_spec_id is NULL) THEN 0 ELSE 1 END as studio +from project p +left join + project_phase pp_reg_open + on p.project_id = pp_reg_open.project_id + and pp_reg_open.phase_type_id = 1 + and pp_reg_open.phase_status_id = 2 +where p.project_id = %d` + +/** + * Perform Challenge unregistration validations + * + * @param userId the userId to use + * @param challengeId the challengeId to use + * @return the ChallengeUnregistrationValidation result + */ +async function performChallengeUnregistrationValidations (userId, challengeId) { + let result = null + const connection = await helper.getInformixConnection() + try { + await connection.beginTransactionAsync() + result = await connection.queryAsync(util.format(QUERY_CHALLENGE_UNREGISTRATION_VALIDATIONS, userId, challengeId)) + await connection.commitTransactionAsync() + } catch (e) { + logger.error(`Error in 'performChallengeUnregistrationValidations' ${e}, rolling back transaction`) + await connection.rollbackTransactionAsync() + throw e + } finally { + await connection.closeAsync() + } + if (result && result.length > 0) { + return result[0] + } + return result +} + +const QUERY_DELETE_USER_RECORD_FROM_PROJECT_RESULT = 'DELETE FROM project_result WHERE project_id = ? and user_id = ?' +const QUERY_DELETE_USER_RECORD_FROM_COMPONENT_INQUIRY = 'DELETE FROM component_inquiry WHERE project_id = ? and user_id = ?' + +/** + * Delete challenge result + * + * @param challengeId the challengeId to use + * @param userId the userId to use + */ +async function deleteChallengeResult (challengeId, userId) { + const connection = await helper.getInformixConnection() + try { + await connection.beginTransactionAsync() + let query = await prepare(connection, QUERY_DELETE_USER_RECORD_FROM_PROJECT_RESULT) + await query.executeAsync([challengeId, userId]) + query = await prepare(connection, QUERY_DELETE_USER_RECORD_FROM_COMPONENT_INQUIRY) + await query.executeAsync([challengeId, userId]) + await connection.commitTransactionAsync() + } catch (e) { + logger.error(`Error in 'deleteChallengeResult' ${e}, rolling back transaction`) + await connection.rollbackTransactionAsync() + throw e + } finally { + logger.info(`Delete challenge result for ${userId} from project ${challengeId}`) + await connection.closeAsync() + } +} + +const QUERY_GET_USER_CHALLENGE_RESOURCE = ` +SELECT DISTINCT + resource_info_type_lu.resource_info_type_id AS resourceInfoTypeId, + resource_info.value AS userId, + resource.resource_id AS resourceId, + resource_role_id AS resourceRoleId +FROM resource, + resource_info, + resource_info_type_lu +WHERE resource.resource_id = resource_info.resource_id + AND resource_info.resource_info_type_id = resource_info_type_lu.resource_info_type_id + AND project_id = %d + AND resource_info_type_lu.resource_info_type_id = 1 + AND resource_info.value = %d` + +/** + * Get user challenge resource + * + * @param challengeId the challengeId to use + * @param userId the userId to use + * @return the UserChallengeResource result + */ +async function getUserChallengeResource (challengeId, userId) { + let result = null + const connection = await helper.getInformixConnection() + try { + await connection.beginTransactionAsync() + result = await connection.queryAsync(util.format(QUERY_GET_USER_CHALLENGE_RESOURCE, challengeId, userId)) + await connection.commitTransactionAsync() + } catch (e) { + logger.error(`Error in 'getUserChallengeResource' ${e}, rolling back transaction`) + await connection.rollbackTransactionAsync() + throw e + } finally { + await connection.closeAsync() + } + return result +} + +const QUERY_DELETE_FROM_RESOURCE_INFO = 'DELETE FROM resource_info WHERE resource_id = ?' +const QUERY_DELETE_FROM_RESOURCE_SUBMISSION = 'DELETE FROM resource_submission WHERE resource_id = ?' +const QUERY_DELETE_FROM_SUBMISSION = 'DELETE FROM submission where upload_id in (select upload_id from upload where resource_id=?)' +const QUERY_DELETE_FROM_UPLOAD = 'DELETE FROM upload where resource_id=?' +const QUERY_DELETE_FROM_RESOURCE = 'DELETE FROM resource WHERE resource_id = ?' + +/** + * Delete challenge resources + * @param resourceId the resourceId to use + */ +async function deleteChallengeResources (resourceId) { + const connection = await helper.getInformixConnection() + try { + await connection.beginTransactionAsync() + let query = await prepare(connection, QUERY_DELETE_FROM_RESOURCE_INFO) + await query.executeAsync([resourceId]) + query = await prepare(connection, QUERY_DELETE_FROM_RESOURCE_SUBMISSION) + await query.executeAsync([resourceId]) + query = await prepare(connection, QUERY_DELETE_FROM_SUBMISSION) + await query.executeAsync([resourceId]) + query = await prepare(connection, QUERY_DELETE_FROM_UPLOAD) + await query.executeAsync([resourceId]) + query = await prepare(connection, QUERY_DELETE_FROM_RESOURCE) + await query.executeAsync([resourceId]) + await connection.commitTransactionAsync() + } catch (e) { + logger.error(`Error in 'deleteChallengeResources' ${e}, rolling back transaction`) + await connection.rollbackTransactionAsync() + throw e + } finally { + logger.info(`Delete challenge resource ${resourceId}`) + await connection.closeAsync() + } +} + +const QUERY_AUDIT_CHALLENGE_REGISTRATION = ` +INSERT INTO project_user_audit + ( project_user_audit_id, + project_id, + resource_user_id, + resource_role_id, + audit_action_type_id, + action_date, + action_user_id + ) +VALUES +( PROJECT_USER_AUDIT_SEQ.nextval, + ?, + ?, + ?, + ?, + CURRENT, + ?)` + +/** + * Audit challenge registration + * + * @param challengeId the challengeId to use + * @param resourceUserId the resourceUserId to use + * @param resourceRoleId the resourceRoleId to use + * @param auditActionTypeId the auditActionTypeId to use + * @param actionUserId the actionUserId to use + */ +async function auditChallengeRegistration (challengeId, resourceUserId, resourceRoleId, auditActionTypeId, actionUserId) { + const connection = await helper.getInformixConnection() + try { + await connection.beginTransactionAsync() + const query = await prepare(connection, QUERY_AUDIT_CHALLENGE_REGISTRATION) + await query.executeAsync([challengeId, resourceUserId, resourceRoleId, auditActionTypeId, actionUserId]) + await connection.commitTransactionAsync() + } catch (e) { + logger.error(`Error in 'persistResourceInfo' ${e}, rolling back transaction`) + await connection.rollbackTransactionAsync() + throw e + } finally { + await connection.closeAsync() + } +} + +const QUERY_GET_CHALLENGE_FORUM = ` +SELECT info.project_id as challengeId, + info_type.name as name, + info.value as forumCategoryId +FROM project_info AS info + JOIN project_info_type_lu AS info_type + ON info.project_info_type_id = info_type.project_info_type_id +WHERE name = 'Developer Forum ID' and info.project_id = %d` + +/** + * Get challenge forum + * + * @param challengeId the challengeId + * @return the ChallengeForum result + */ +async function getChallengeForum (challengeId) { + let result = null + const connection = await helper.getInformixConnection() + try { + await connection.beginTransactionAsync() + result = await connection.queryAsync(util.format(QUERY_GET_CHALLENGE_FORUM, challengeId)) + await connection.commitTransactionAsync() + } catch (e) { + logger.error(`Error in 'getChallengeForum' ${e}, rolling back transaction`) + await connection.rollbackTransactionAsync() + throw e + } finally { + await connection.closeAsync() + } + return result +} + +module.exports = { + performChallengeUnregistrationValidations, + deleteChallengeResult, + getUserChallengeResource, + deleteChallengeResources, + auditChallengeRegistration, + getChallengeForum +} diff --git a/src/dao/UserDAO.js b/src/dao/UserDAO.js new file mode 100644 index 0000000..6e7bd7f --- /dev/null +++ b/src/dao/UserDAO.js @@ -0,0 +1,26 @@ +const util = require('util') +const helper = require('../common/helper') +const logger = require('../common/logger') + +const QUERY_GET_USER_HANDLE = 'select handle from user where user_id = %d' + +async function getUserHandle (userId) { + let handle = null + const connection = await helper.getInformixConnection() + try { + await connection.beginTransactionAsync() + handle = await connection.queryAsync(util.format(QUERY_GET_USER_HANDLE, userId)) + await connection.commitTransactionAsync() + } catch (e) { + logger.error(`Error in 'getUserHandle' ${e}, rolling back transaction`) + await connection.rollbackTransactionAsync() + throw e + } finally { + await connection.closeAsync() + } + return handle +} + +module.exports = { + getUserHandle +} diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index dba3cfc..1e56dfd 100755 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -6,6 +6,9 @@ const Joi = require('joi') const config = require('config') const logger = require('../common/logger') const helper = require('../common/helper') +const RegistrationManager = require('./RegistrationManager') +const UnRegistrationManager = require('./UnRegistrationManager') +const ResourceDirectManager = require('./ResourceDirectManager') const {isStudio} = require('../common/utils') /** @@ -71,29 +74,26 @@ async function _updateChallengeResource (message, isDelete) { resourceRole = resourceRoleResponse.body[0] logger.debug(`Resource Role Response ${JSON.stringify(resourceRole)}`) const userId = _.get(message, 'payload.memberId') - const body = { - roleId: resourceRole.legacyId, - resourceUserId: userId, - isStudio: isStudio(v5Challenge.type) - } - + const resourceRoleId = resourceRole.legacyId + const projectId = _.get(v5Challenge, 'legacyId') + const isStudioChallenge = isStudio(v5Challenge.type) // create or delete the challenge resource from V4 API let response = null if (resourceRole.id === config.SUBMITTER_ROLE_ID) { if (isDelete) { - logger.debug(`Unregistering Submitter ${config.CHALLENGE_API_V4_URL}/${_.get(v5Challenge, 'legacyId')}/unregister?userId=${userId} - ${JSON.stringify(body)}`) - response = await helper.postRequest(`${config.CHALLENGE_API_V4_URL}/${_.get(v5Challenge, 'legacyId')}/unregister?userId=${userId}`, {}, m2mToken) + logger.debug(`Unregistering submitter ${userId} from project ${projectId}`) + await UnRegistrationManager.unregisterChallenge(userId, projectId) } else { - logger.debug(`Registering Submitter ${config.CHALLENGE_API_V4_URL}/${_.get(v5Challenge, 'legacyId')}/register?userId=${userId} - ${JSON.stringify(body)}`) - response = await helper.postRequest(`${config.CHALLENGE_API_V4_URL}/${_.get(v5Challenge, 'legacyId')}/register?userId=${userId}`, {}, m2mToken) + logger.debug(`Registering submitter ${userId} to project ${projectId}`) + await RegistrationManager.registerChallenge(userId, projectId, false) } } else { if (isDelete) { - logger.debug(`Deleteing Challenge Resource ${config.CHALLENGE_API_V4_URL}/${_.get(v5Challenge, 'legacyId')}/resources - ${JSON.stringify(body)}`) - response = await helper.deleteRequest(`${config.CHALLENGE_API_V4_URL}/${_.get(v5Challenge, 'legacyId')}/resources`, body, m2mToken) + logger.debug(`Deleteing Challenge Resource ${userId} from project ${projectId}`) + await ResourceDirectManager.removeResource(userId, projectId, resourceRoleId, isStudioChallenge) } else { - logger.debug(`Creating Challenge Resource ${config.CHALLENGE_API_V4_URL}/${_.get(v5Challenge, 'legacyId')}/resources - ${JSON.stringify(body)}`) - response = await helper.postRequest(`${config.CHALLENGE_API_V4_URL}/${_.get(v5Challenge, 'legacyId')}/resources`, body, m2mToken) + logger.debug(`Creating Challenge Resource ${userId} to project ${projectId}`) + await ResourceDirectManager.addResource(userId, projectId, resourceRoleId, isStudioChallenge) } logger.debug(`Update Challenge Response ${JSON.stringify(response)}`) } diff --git a/src/services/ProjectService.js b/src/services/ProjectService.js new file mode 100644 index 0000000..4a578fa --- /dev/null +++ b/src/services/ProjectService.js @@ -0,0 +1,127 @@ +const helper = require('../common/helper') +const Constants = require('../constants') + +const QUERY_CHECK_RESOURCE_EXISTS = 'SELECT COUNT(*) as num FROM resource WHERE project_id = %d AND resource_role_id = %d AND user_id = %d' + +/** + * Check resource exists or not + * @param challengeId the challengeId to pass + * @param roleId the userId to pass + * @param userId the userId to pass + * @return the result + */ +async function resourceExists (challengeId, roleId, userId) { + const result = helper.queryDataFromDB(QUERY_CHECK_RESOURCE_EXISTS, [challengeId, roleId, userId]) + if (result && result.length > 0) { + return result[0].num > 0 + } + return false +} + +const QUERY_GET_RESOURCES = ` +SELECT + resource_id as resourceId, + user_id as userId, + project_id as projectId, + resource_role_id as resourceRoleId +FROM resource where project_id = %d and resource_role_id = %d` + +/** + * Get all resources with challengeId and roleId + * @param challengeId the challengeId to pass + * @param roleId the userId to pass + * @return the result + */ +async function searchResources (challengeId, roleId) { + return helper.queryDataFromDB(QUERY_GET_RESOURCES, [challengeId, roleId]) +} + +const QUERY_GET_ALL_RESOURCE_ROLES = 'SELECT * FROM resource_role_lu' + +/** + * Get all resource roles + * @returns all resource roles + */ +async function getAllResourceRoles () { + return helper.queryDataFromDB(QUERY_GET_ALL_RESOURCE_ROLES, []) +} + +const QUERY_INSERT_NOTIFICATION = ` +INSERT INTO notification (project_id, external_ref_id, notification_type_id, create_user, create_date, modify_user, modify_date) +VALUES (?, ?, ?, ?, CURRENT, ?, CURRENT)` + +/** + * Get all resources with challengeId and roleId + * @param contestId the challengeId to pass + * @param externalRefId the external ref id + * @param notificationTypeId the notification type id + * @param operatorId the operator id + */ +async function addNotifications (contestId, externalRefId, notificationTypeId, operatorId) { + await helper.executeSQLonDB(QUERY_INSERT_NOTIFICATION, [contestId, externalRefId, notificationTypeId, operatorId, operatorId]) +} + +const QUERY_DELETE_NOTIFICATION = 'DELETE FROM notification WHERE project_id = ? AND external_ref_id = ? AND notification_type_id = ?' +/** + * Get all resources with challengeId and roleId + * @param userId the challengeId to pass + * @param contestId the challengeId to pass + */ +async function removeNotifications (userId, contestId) { + await helper.executeSQLonDB(QUERY_DELETE_NOTIFICATION, [contestId, userId, Constants.TIMELINE_NOTIFICATION_ID]) +} + +const QUERY_GET_FORUM_ID = 'select value from project_info where project_info_type_id = 4 and project_id = %d' + +async function getForumId (challengeId) { + const result = await helper.queryDataFromDB(QUERY_GET_FORUM_ID, [challengeId]) + if (result && result.length > 0) { + return result[0].value + } + return result +} + +const QUERY_DELETE_RES_INFO = 'DELETE FROM resource_info WHERE resource_id = ?' +const QUERY_DELETE_SUBMISSION = 'DELETE FROM resource_submission WHERE resource_id = ?' +const QUERY_DELETE_RESOURCE = 'DELETE FROM resource WHERE resource_id = ?' +/** + * Remove resource + * @param resource the resource obj + * @param operatorId operator id + */ +async function removeResource (resource) { + await helper.executeSQLonDB(QUERY_DELETE_RES_INFO, [resource.resourceid]) + await helper.executeSQLonDB(QUERY_DELETE_SUBMISSION, [resource.resourceid]) + await helper.executeSQLonDB(QUERY_DELETE_RESOURCE, [resource.resourceid]) + // audit deletion + await auditProjectUser(resource, Constants.PROJECT_USER_AUDIT_DELETE_TYPE) // delete +} + +const QUERY_INSERT_PROJECT_USER_AUDIT = ` +INSERT INTO project_user_audit (project_user_audit_id, project_id, resource_user_id, + resource_role_id, audit_action_type_id, action_date, action_user_id) +VALUES (PROJECT_USER_AUDIT_SEQ.nextval, ?, ?, ?, ?, CURRENT, ?)` + +/** + * This method will audit project user information. This information is generated when a resource is added, + * deleted or changes its user or role. + * + * @param connection the connection to database + * @param resource the resource being audited + * @param auditType the audit type. Can be PROJECT_USER_AUDIT_CREATE_TYPE or PROJECT_USER_AUDIT_DELETE_TYPE. + * @param userId the resource user id. This value overrides the value inside resource if present. + * @param resourceRoleId the resource role id. This value overrides the value inside resource if present. + */ +async function auditProjectUser (resource, auditType) { + await helper.executeSQLonDB(QUERY_INSERT_PROJECT_USER_AUDIT, [resource.projectid, resource.userid, resource.resourceroleid, auditType, resource.userid]) +} + +module.exports = { + resourceExists, + searchResources, + getAllResourceRoles, + addNotifications, + removeNotifications, + getForumId, + removeResource +} diff --git a/src/services/RegistrationManager.js b/src/services/RegistrationManager.js new file mode 100644 index 0000000..c914080 --- /dev/null +++ b/src/services/RegistrationManager.js @@ -0,0 +1,308 @@ +const RegistrationDAO = require('../dao/RegistrationDAO') +const SequenceDAO = require('../dao/SequenceDAO') +const UserDAO = require('../dao/UserDAO') +const Constants = require('../constants') +const logger = require('../common/logger') +const ForumWrapper = require('../dao/ForumWrapper') + +async function getAllGroupIds (userId) { + // try { + // const groupIds = await groupServiceClient.getGroups(userId) + // return groupIds + // } catch (e) { + // throw e + // } + return [] +} + +/** + * Check user challenge eligibility + * @param user the user to pass + * @param userId the userId to pass + * @param challengeId the challengeId to pass + * @throws SupplyException if any error occurs + */ +async function checkUserChallengeEligibility (userId, challengeId) { + const groups = await RegistrationDAO.getChallengeAccessibilityAndGroups(userId, challengeId) + // If there's no corresponding record in group_contest_eligibility + // then the challenge is available to all users + if (!groups || groups.length === 0) { + return + } + const groupInd = groups[0].challenge_group_ind + if (!groupInd) { + return + } + await getAllGroupIds(userId) + // const groupIds = await getAllGroupIds(userId) +} + +/** + * Register component inquiry + * @param userId the userId to pass + * @param challengeId the challengeId to pass + * @throws Error if any error occurs + * @return ComponentInfo + */ +async function registerComponentInquiry (userId, challengeId) { + const compInfos = await RegistrationDAO.getComponentInfo(challengeId) + if (!compInfos || compInfos.length < 1) { + throw new Error('component not found when register component inquiry') + } + const compInfo = compInfos[0] + const userRating = await RegistrationDAO.getUserRating(userId, compInfo.projectcategoryid + 111) + let rating = null + if (userRating && userRating.length > 0) { + rating = userRating[0].rating + } + const nextId = await SequenceDAO.getCompInQuerySeqNextId() + const phase = compInfo.projectcategoryid === Constants.DESIGN_PROJECT_TYPE || compInfo.projectcategoryid === Constants.DEVELOPMENT_PROJECT_TYPE ? compInfo.projectcategoryid : null + await RegistrationDAO.insertRegistrationRecord(nextId, compInfo.componentid, userId, compInfo.comments, 1, rating, phase, userId, compInfo.version, challengeId) + compInfo.rating = rating + return compInfo +} + +/** + * Check if the rating suit for software category contests. + * The code logic is duplicated from server-side java code. + * + * @param phaseId the phase id. + * @param projectCategoryId the category id. + * @return true if the rating is suitable for development (software) category challenge, otherwise false. + */ +function isRatingSuitableDevelopment (phaseId, projectCategoryId) { + // The rating is suitable for software, e.g. not for studio. + let suitable = false + if (projectCategoryId === Constants.COMPONENT_TESTING_PROJECT_TYPE) { + if (phaseId === 113) { + suitable = true + } + } else if (projectCategoryId + 111 === phaseId) { + suitable = true + } + return suitable +} + +/** + * Project track + * @param userId the userId to pass + * @param challengeId the challengeId to pass + * @param compInfo the compInfo to pass + * @throws Error if any error occurs + */ +async function projectTrack (userId, challengeId, compInfo) { + const resourceId = await SequenceDAO.getResourceSeqNextId() + await RegistrationDAO.persistResource(userId, challengeId, resourceId) + + await RegistrationDAO.auditChallengeRegistration(challengeId, userId, Constants.SUBMITTER_RESOURCE_ROLE_ID, Constants.PROJECT_USER_AUDIT_CREATE_TYPE, userId) + + let rating = compInfo.rating + if (!isRatingSuitableDevelopment(compInfo.phaseid, compInfo.projectcategoryid)) { + rating = null + } + await RegistrationDAO.insertChallengeResult(challengeId, userId, 0, 0, rating) + await RegistrationDAO.persistResourceInfo(userId, resourceId, 1, '' + userId) + const res = await UserDAO.getUserHandle(userId) + if (!res || res.length < 1) { + throw new Error('user\'s handle not found') + } + const handle = res[0].handle + + await RegistrationDAO.persistResourceInfo(userId, resourceId, 2, handle) + if (compInfo.rating != null && compInfo.rating > 0) { + await RegistrationDAO.persistResourceInfo(userId, resourceId, 4, '' + compInfo.rating) + } + const rr = await RegistrationDAO.getUserReliability(userId, challengeId) + if (rr && rr.length > 0) { + const rel = +rr[0].rating + await RegistrationDAO.persistResourceInfo(userId, resourceId, 5, '' + rel * 100) + } + await RegistrationDAO.persistResourceInfo(userId, resourceId, 6, new Date()) + await RegistrationDAO.persistResourceInfo(userId, resourceId, Constants.APPEALS_COMPLETE_EARLY_PROPERTY_ID, Constants.NO_VALUE) +} + +/** + * Register challenge + * @param userId the userId to pass + * @param challengeId the challengeId to pass + * @param challengeType the challengeType to pass + * @throws Error if any error occurs + */ +async function registerChallengeByType (userId, challengeId, challengeType) { + const compInfo = await registerComponentInquiry(userId, challengeId) + if (challengeType === Constants.DEVELOPMENT_PROJECT_TYPE) { + await projectTrack(userId, challengeId, compInfo) + } + + const ns = await RegistrationDAO.getChallengeNotificationCount(challengeId, userId, Constants.TIMELINE_NOTIFICATION_ID) + if (!ns || ns.length < 1) { + throw new Error('Notification not found.') + } + if (ns[0].total_count === 0) { + await RegistrationDAO.insertChallengeNotification(challengeId, userId, 1) + } + if (compInfo.componentid <= 0) { + throw new Error('Could not find component for challenge') + } + const cId = await RegistrationDAO.getActiveForumCategory(compInfo.componentid) + let categoryForumId = 0 + if (!cId || cId.length < 1) { + logger.debug('Could not find component for challenge ' + challengeId) + } else { + categoryForumId = cId[0].jive_category_id + } + + if (categoryForumId > 0) { + if (challengeType === Constants.DEVELOPMENT_PROJECT_TYPE) { + try { + logger.debug('start to grant user ' + userId + ' forum category ' + categoryForumId + ' access.') + await ForumWrapper.createCategoryWatch(userId, categoryForumId) + await ForumWrapper.assignRole(userId, 'Software_Users_' + categoryForumId) + } catch (exp) { + throw new Error('Failed to create the forum wrapper' + exp) + } + } + } + + // TODO: implement similar logic for below java code + // this.sendEmailNotification(userId, challengeId, challengeType, categoryForumId, compInfo); +} + +/** + * Check all terms of use + * @param userId the userId to pass + * @param challengeId the challengeId to pass + * @return true if agreed + */ +async function allUseOfTermsAgreed (userId, challengeId) { + const roles = await RegistrationDAO.getResourceRoles() + let submitterRole = null + for (const role of roles) { + if (role.name.toLowerCase() === 'submitter') { + submitterRole = role + break + } + } + if (!submitterRole) { + return false + } + const terms = await RegistrationDAO.getUseTermsOfAgree(userId, challengeId, submitterRole.id) + for (const term of terms) { + if (!term.agreed) { + return false + } + } + return true +} + +/** + * Register studio challenge + * @param userId the userId to pass + * @param challengeId the challengeId to pass + */ +async function registerStudioChallenge (userId, challengeId) { + // check terms + const agreed = await allUseOfTermsAgreed(userId, challengeId) + if (!agreed) { + throw new Error('You should agree with all terms of use.') + } + const resourceId = await SequenceDAO.getResourceSeqNextId() + await RegistrationDAO.persistResource(userId, challengeId, resourceId) + await RegistrationDAO.persistResourceInfo(userId, resourceId, 1, '' + userId) + const res = await UserDAO.getUserHandle(userId) + if (!res || res.length < 1) { + throw new Error('user\'s handle not found') + } + const handle = res[0].handle + await RegistrationDAO.persistResourceInfo(userId, resourceId, 2, handle) + await RegistrationDAO.persistResourceInfo(userId, resourceId, 6, new Date()) // new Date() is not used at last + await RegistrationDAO.persistResourceInfo(userId, resourceId, 8, 'N/A') + await registerChallengeByType(userId, challengeId, Constants.DESIGN_PROJECT_TYPE) +} + +async function registerSoftwareChallenge (userId, challengeId) { + const isCopilotPosting = await RegistrationDAO.checkChallengeIsCopilotPosting(challengeId) + const isCopilot = await RegistrationDAO.checkIsCopilot(userId) + if (isCopilotPosting && !isCopilot) { + throw new Error('You should be a copilot before register a copilot posting.') + } + + // check terms + const agreed = await allUseOfTermsAgreed(userId, challengeId) + if (!agreed) { + throw new Error('You should agree with all terms of use.') + } + await registerChallengeByType(userId, challengeId, Constants.DEVELOPMENT_PROJECT_TYPE) +} + +/** + * Validate challenge registration + * @param userId the userId to pass + * @param challengeId the challengeId to pass + */ +async function validateChallengeRegistration (userId, challengeId) { + const valid = await RegistrationDAO.validateChallengeRegistration(userId, challengeId) + if (!valid.regopen) { + throw new Error('Registration Phase of this challenge is not open.') + } + if (valid.userregistered) { + throw new Error('You are already registered for this challenge.') + } + if (valid.user_suspended) { + throw new Error('You cannot participate in this challenge due to suspension.') + } + if (valid.usercountrybanned) { + throw new Error('You are not eligible to participate in this challenge because ' + + 'of your country of residence. Please see our terms of service for more information.') + } + if (valid.compcountryisnull) { + throw new Error('You are not eligible to participate in this challenge because you ' + + 'have not specified your country of residence. Please go to your Settings and enter a country. ' + + 'Please see our terms of service for more information.') + } + + if (valid.projectcategoryid === Constants.COPILOT_POSTING_PROJECT_TYPE) { + if (!valid.useriscopilot && valid.copilottype != null && valid.copilottype.contains('Marathon Match')) { + throw new Error('You cannot participate in this challenge because you are not an active member of the copilot pool.') + } + } +} + +/** + * Register challenge + * + * @param userId the user id + * @param challengeId the challengeId to use + * @param isAdmin is admin user or not + */ +async function registerChallenge (userId, challengeId, isAdmin) { + // check user activated + const us = await RegistrationDAO.checkUserActivated(userId) + if (!us || us[0].status !== 'A') { + throw new Error('You must activate your account in order to participate. ' + + 'Please check your e-mail in order to complete the activation process, ' + + 'or contact support@topcoder.com if you did not receive an e-mail.') + } + // check challenge exists + const cs = await RegistrationDAO.checkChallengeExists(challengeId) + if (!cs || cs.length === 0) { + throw new Error('The challenge does not exist.') + } + if (!isAdmin) { + // check user eligibility + await checkUserChallengeEligibility(userId, challengeId) + } + // validate the challenge + await validateChallengeRegistration(userId, challengeId) + + const isStudio = +cs[0].is_studio + if (isStudio) { + await registerStudioChallenge(userId, challengeId) + } else { + await registerSoftwareChallenge(userId, challengeId) + } +} + +module.exports = { + registerChallenge +} diff --git a/src/services/ResourceDirectManager.js b/src/services/ResourceDirectManager.js new file mode 100644 index 0000000..b9636b9 --- /dev/null +++ b/src/services/ResourceDirectManager.js @@ -0,0 +1,199 @@ +const ProjectServices = require('./ProjectService') +const RegistrationDAO = require('../dao/RegistrationDAO') +const config = require('config') +const helper = require('../common/helper') +const SequenceDAO = require('../dao/SequenceDAO') +const Constants = require('../constants') +const ForumsWrapper = require('../dao/ForumWrapper') + +/** + * create software forum watch with given parameters. + * @param forumId + * The forum id to use + * @param userId + * The user id to use + * @param watch + * If category watch is to be created + */ +async function createSoftwareForumWatchAndRole (forumId, userId, watch) { + const roleId = 'Software_Moderators_' + forumId + if (watch) { + await ForumsWrapper.createCategoryWatch(userId, forumId) + } + + await ForumsWrapper.assignRole(userId, roleId) +} + +/** + * create software forum watch with given parameters. + * @param forumId + * The forum id to use + * @param userId + * The user id to use + */ +async function removeSoftwareForumWatchAndRole (forumId, userId) { + const forumRoleId = 'Software_Moderators_' + forumId + await ForumsWrapper.deleteCategoryWatch(userId, forumId) + await ForumsWrapper.removeRole(userId, forumRoleId) +} + +/** + * Assign the given roleId to the specified userId in the given project. + * @param operatorId + * the operator id. + * @param contestId + * the id of the contest. + * @param roleId + * the id of the role. + * @param userId + * the id of the user. + * @param phase + * the Phase associated with the resource. + * @param addNotification + * whether to add notification. + * @param addForumWatch + * whether to add forum watch. + * @param isStudio + * whether assign to studio contest. + * @param checkTerm + * whether to check terms and conditions. + */ +async function assignRole (operatorId, contestId, roleId, userId, phase, addNotification, addForumWatch, isStudio, checkTerm) { + let found = await ProjectServices.resourceExists(contestId, roleId, userId) + // TODO: suppose termChecking is ok and eligible + // const termChecking = !checkTerm || checkTerms(contestId, userId, new int[] { (int) roleId }) + // const eligible = isEligible(userId, contestId, false) + const termChecking = true + const eligible = true + if (found) { + throw new Error('User ' + userId + ' with role ' + roleId + ' already exists') + } + // if not found && user agreed terms (if any) && is eligible, add resource + if (!found && termChecking && eligible) { + const allroles = await ProjectServices.getAllResourceRoles() + let roleToSet = null + if (allroles && allroles.length > 0) { + for (const role of allroles) { + if (role.resource_role_id === roleId) { + roleToSet = role + } + } + } + if (!roleToSet) { + throw new Error('Invalid role id ' + roleId) + } + const resourceId = await SequenceDAO.getResourceSeqNextId() + await RegistrationDAO.persistResourceWithRoleId(userId, contestId, resourceId, roleId) + + // only check notification setting for observer, else always add + if (roleId !== Constants.RESOURCE_ROLE_OBSERVER_ID || addNotification) { + await ProjectServices.addNotifications(contestId, userId, Constants.TIMELINE_NOTIFICATION_ID, operatorId) + } + // create forum watch + const forumId = await ProjectServices.getForumId(contestId) + + // only check notification for observer + if (roleId !== Constants.RESOURCE_ROLE_OBSERVER_ID) { + addForumWatch = true + } + + if (forumId > 0 && config.IS_CREATE_FORUM && !isStudio) { + await createSoftwareForumWatchAndRole(forumId, userId, addForumWatch) + } + } +} + +/** + * Assign the given roleId to the specified userId in the given project. + * @param operatorId + * the operator id. + * @param contestId + * the id of the contest. + * @param roleId + * the id of the role. + * @param userId + * the id of the user. + * @param phase + * the Phase associated with the resource. + * @param addNotification + * whether to add notification. + * @param addForumWatch + * whether to add forum watch. + * @param isStudio + * whether assign to studio contest. + * @param checkTerm + * whether to check terms and conditions. + */ +async function removeRole (operatorId, contestId, roleId, userId, phase, addNotification, addForumWatch, isStudio, checkTerm) { + let found = await ProjectServices.resourceExists(contestId, roleId, userId) + if (!found) { + throw new Error('User ' + userId + ' does not have role ' + roleId + ' for the project ' + contestId) + } + const resources = await ProjectServices.searchResources(contestId, roleId) + for (const resource of resources) { + if (+resource.userid === userId) { + await ProjectServices.removeResource(resource) + } + } + // always try to remove notifications. this method does nothing if notification does not exist + await ProjectServices.removeNotifications(userId, contestId) + const forumId = ProjectServices.getForumId(contestId) + if (forumId > 0 && config.IS_CREATE_FORUM && !isStudio) { + await removeSoftwareForumWatchAndRole(forumId, userId) + } +} + +/** + * Add resource + * + * @param operatorId the operator id + * @param challengeId The id of the challenge to which to add the resource. + * @param resourceRoleId The id of the resource role + * @param isStudio The is studio or not + */ +async function addResource (operatorId, challengeId, resourceRoleId, isStudio) { + await assignRole(operatorId, challengeId, resourceRoleId, operatorId, null, false, false, isStudio, false) + await fireEvent('ADD_RESOURCE', challengeId, operatorId, resourceRoleId, operatorId, isStudio) +} + +/** + * Remove resource + * + * @param operatorId the operator id + * @param challengeId The id of the challenge to which to add the resource. + * @param resourceRoleId The id of the resource role + * @param isStudio The is studio or not + */ +async function removeResource (operatorId, challengeId, resourceRoleId, isStudio) { + await removeRole(operatorId, challengeId, resourceRoleId, operatorId, null, false, false, isStudio, false) + await fireEvent('REMOVE_RESOURCE', challengeId, operatorId, resourceRoleId, operatorId, isStudio) +} + +/** + * Fire event + * + * @param type the message type + * @param challengeId the challengeId to use + * @param userId the user id + * @param resourceRoleId the user id + * @param operatorId the user id + * @param isStudio is studio or not + */ +async function fireEvent (type, challengeId, userId, resourceRoleId, operatorId, isStudio) { + const message = { + type, + detail: { + challengeId, + userId, + resourceRoleId, + operatorId, + isStudio + } + } + await helper.postBusEvent(config.CHALLENGE_USER_UNREGISTRATION_TOPIC, message) +} + +module.exports = { + addResource, + removeResource +} diff --git a/src/services/UnRegistrationManager.js b/src/services/UnRegistrationManager.js new file mode 100644 index 0000000..69184ae --- /dev/null +++ b/src/services/UnRegistrationManager.js @@ -0,0 +1,92 @@ +const UnRegistrationDAO = require('../dao/UnRegistrationDAO') +const logger = require('../common/logger') +const helper = require('../common/helper') +const config = require('../../config/default') +const Constants = require('../constants') +const ForumWrapper = require('../dao/ForumWrapper') +const EsFeederServiceClient = require('../client/EsFeederServiceClient') + +async function unregisterChallenge (userId, challengeId) { + const regValidation = await UnRegistrationDAO.performChallengeUnregistrationValidations(userId, challengeId) + if (!regValidation) { + throw new Error('No such challenge exists.') + } + if (!regValidation.regopen) { + throw new Error('You cannot unregister since registration phase is not open.') + } + if (!regValidation.userregistered) { + throw new Error('You are not registered for this challenge.') + } + if (!regValidation.studio) { + await UnRegistrationDAO.deleteChallengeResult(challengeId, userId) + } + const resources = await UnRegistrationDAO.getUserChallengeResource(challengeId, userId) + if (!resources || resources.length < 1) { + logger.error('Could not find user challenge resource') + throw new Error(`Could not find user ${userId} from challenge ${challengeId}.`) + } + let toDelete = null + for (const res of resources) { + if (res.resourceroleid === Constants.SUBMITTER_RESOURCE_ROLE_ID) { + toDelete = res + break + } + } + if (toDelete) { + await UnRegistrationDAO.deleteChallengeResources(toDelete.resourceid) + await UnRegistrationDAO.auditChallengeRegistration(challengeId, userId, Constants.SUBMITTER_RESOURCE_ROLE_ID, Constants.PROJECT_USER_AUDIT_DELETE_TYPE, userId) + } + // remove forum permission for dev challenge. No forum permission for design challenge right now + if (!regValidation.studio) { + // Only remove forum permissions if the user has no other roles left. + if (resources.length === 1 && resources[0].resourceroleid === Constants.SUBMITTER_RESOURCE_ROLE_ID) { + const forums = await UnRegistrationDAO.getChallengeForum(challengeId) + if (!forums || forums.length < 1) { + logger.error('Could not find user challenge forum') + throw new Error('Could not find user challenge forum') + } + const forumCategoryId = forums[0].forumcategoryid + if (!forumCategoryId && forumCategoryId === 0) { + return + } + if (forumCategoryId === null) { + logger.error('Could not find forum category') + throw new Error('Could not find forum category') + } + logger.info('start to remove user ' + userId + ' from forum category ' + forumCategoryId + '.') + try { + await ForumWrapper.removeRole(userId, 'Software_Users_' + forumCategoryId) + await ForumWrapper.removeRole(userId, 'Software_Moderators_' + forumCategoryId) + await ForumWrapper.removeUserPermission(userId, forumCategoryId) + await ForumWrapper.deleteCategoryWatch(userId, forumCategoryId) + } catch (exp) { + logger.error(exp) + } + } + } + // TODO: implement following logic in nodejs + await fireEvent(challengeId, userId) + logger.info('notify es es') + await EsFeederServiceClient.notifyChallengeChange(challengeId) +} + +/** + * Fire event + * + * @param challengeId the challengeId to use + * @param userId the user id + */ +async function fireEvent (challengeId, userId) { + const message = { + type: 'USER_UNREGISTRATION', + detail: { + challengeId, + userId + } + } + await helper.postBusEvent(config.CHALLENGE_USER_UNREGISTRATION_TOPIC, message) +} + +module.exports = { + unregisterChallenge +}