diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..02605ae785e5d456dfb307d068d559b7c47bbb56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/target/ +*.log + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..8240d14feff2e6c372a43e898c595869e7015c7d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM openjdk:8 + +# Define working directory. +ENV WORKING_DIR /opt/nynja +# Install curl for use with Kubernetes readiness probe. +RUN mkdir -p "$WORKING_DIR" \ + && apt-get update \ + && apt-get install -y curl \ + && rm -rf /var/lib/apt/lists/* +WORKDIR $WORKING_DIR + +# Copy the .jar file into the Docker image +COPY ./target/*.jar $WORKING_DIR/airdrop-service.jar + +CMD ["java", "-jar", "airdrop-service.jar"] \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000000000000000000000000000000000000..69fa08baf7a0328597f8f38359841740edcb3d39 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,184 @@ +#!/usr/bin/env groovy + +@Library('nynja-common') _ + +pipeline { + environment { + SLACK_CHANNEL = "#nynja-devops-feed" + NAMESPACE = "airdrop" + APP_NAME = "airdrop-service" + IMAGE_NAME = "eu.gcr.io/nynja-ci-201610/${NAMESPACE}/${APP_NAME}" + IMAGE_BUILD_TAG = "$BRANCH_NAME-$BUILD_NUMBER" + HELM_CHART_NAME = "airdrop-service" + DEV_BRANCH = "develop" + } + agent { + kubernetes(builders.multi([ + "mvn":"maven:3-jdk-10", + "helm":"lachlanevenson/k8s-helm:v2.9.1" + ])) + } + options { + skipDefaultCheckout() + buildDiscarder(logRotator(numToKeepStr: '15')) + } + stages { + stage('Checkout') { + steps { + container('mvn') { + script { + def vars = checkout scm + vars.each { k,v -> env.setProperty(k, v) } + } + slackSend channel: SLACK_CHANNEL, message: slackStartMsg() + slackSend channel: SLACK_CHANNEL, message: "", attachments: slackBuildInfo() + } + } + } + stage('Build PR') { + when { + branch 'PR-*' + } + stages { + stage('Build') { + steps { + container('mvn') { + withCredentials([file(credentialsId: 'mavenSettings.xml', variable: 'FILE')]) { + sh 'mvn --settings $FILE clean install -DskipTests=true' + } + } + } + } + } + } + stage('Build commits') { + when { + not { + anyOf { + branch env.DEV_BRANCH; + branch 'master' + branch 'PR-*' + } + } + } + stages { + stage('Build') { + steps { + container('mvn') { + withCredentials([file(credentialsId: 'mavenSettings.xml', variable: 'FILE')]) { + sh 'mvn --settings $FILE clean install -DskipTests=true' + } + } + } + } + } + } + + stage('Build Dev') { + when { + branch env.DEV_BRANCH + } + stages { + stage('Build') { + steps { + container('mvn') { + withCredentials([file(credentialsId: 'mavenSettings.xml', variable: 'FILE')]) { + sh 'mvn --settings $FILE clean install -DskipTests=true' + } + dockerBuildAndPushToRegistry "${NAMESPACE}/${APP_NAME}", [IMAGE_BUILD_TAG] + } + } + } + stage("Helm chart") { + steps { + container('helm') { + helmBuildAndPushToRegistry HELM_CHART_NAME + } + } + } + stage('Deploy preview') { + steps { + deployHelmTo "dev", NAMESPACE + } + } + } + post { + success { + container('mvn') { slackSend channel: SLACK_CHANNEL, message: slackEndMsg(), color: 'good' } + } + failure { + container('mvn') { slackSend channel: SLACK_CHANNEL, message: slackEndMsg(), color: 'danger' } + } + } + } + stage('Build Release') { + when { + branch 'master' + } + stages { + stage("Build") { + steps { + container('mvn') { + withCredentials([file(credentialsId: 'mavenSettings.xml', variable: 'FILE')]) { + sh 'mvn --settings $FILE clean install -DskipTests=true' + } + dockerBuildAndPushToRegistry "${NAMESPACE}/${APP_NAME}", [IMAGE_BUILD_TAG] + } + } + } + stage("Helm chart") { + steps { + container('helm') { + helmBuildAndPushToRegistry HELM_CHART_NAME + } + } + } + stage("Approval: Deploy to staging ?") { + steps { + slackSend channel: SLACK_CHANNEL, message: "$APP_NAME: build #$BUILD_NUMBER ready to deploy to `STAGING`, approval required: $BUILD_URL (24h)" + + timeout(time: 24, unit: 'HOURS') { input 'Deploy to staging ?' } + } + post { failure { echo 'Deploy aborted for build #...' }} + } + stage("Deploy to staging") { + steps { + slackSend channel: SLACK_CHANNEL, message: "$APP_NAME: deploying build #$BUILD_NUMBER to `STAGING`" + deployHelmTo "staging", NAMESPACE + } + } + stage("Approval: Deploy to production ?") { + steps { + slackSend channel: SLACK_CHANNEL, message: "$APP_NAME: build #$BUILD_NUMBER ready to deploy to `PRODUCTION`, approval required: $BUILD_URL (24h)" + + timeout(time: 7, unit: 'DAYS') { input 'Deploy to production ?' } + } + post { failure { echo 'Deploy aborted for build #...' }} + } + stage('Tagging release') { + steps { + container("mvn") { + // Updating the "latest tag" + dockerTagLatestAndPushToRegistry "${NAMESPACE}/${APP_NAME}", IMAGE_BUILD_TAG + } + } + } + /* + stage('Deploy release to canary') { + steps { + slackSend channel: SLACK_CHANNEL, message: "$APP_NAME: deploying build #$BUILD_NUMBER to `PRODUCTION` (canary)" + echo "deploy to canary" + } + } + */ + stage("Deploy to production") { + steps { + slackSend channel: SLACK_CHANNEL, message: "$APP_NAME: deploying build #$BUILD_NUMBER to `PRODUCTION`" + + deployHelmTo "prod", NAMESPACE + } + } + } + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index c6eff9a9f19bc8c217f00b582439110f6476ebc0..1d6900083fc9809fe6268537d42b5bcf0b874b2b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # airdrop-service + Nynja service for qoinpro integration diff --git a/charts/airdrop-service/.helmignore b/charts/airdrop-service/.helmignore new file mode 100644 index 0000000000000000000000000000000000000000..f0c13194444163d1cba5c67d9e79231a62bc8f44 --- /dev/null +++ b/charts/airdrop-service/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj diff --git a/charts/airdrop-service/Chart.yaml b/charts/airdrop-service/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7626240fd1ce03809df462bd707016e3654d6a84 --- /dev/null +++ b/charts/airdrop-service/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +appVersion: "1.0" +description: Airdrop service. +name: airdrop-service +version: 0.1.2 diff --git a/charts/airdrop-service/templates/00-label.yaml b/charts/airdrop-service/templates/00-label.yaml new file mode 100644 index 0000000000000000000000000000000000000000..dd5d17afc13ed355afb9d48f5d41bb161f685ccc --- /dev/null +++ b/charts/airdrop-service/templates/00-label.yaml @@ -0,0 +1,32 @@ +# This hook depends on helm creating the target namespace if it doesn't exist +# before the hook is called. This is the case on Helm v2.9.1 +apiVersion: batch/v1 +kind: Job +metadata: + name: enable-istio-injection-{{ .Release.Namespace }} + namespace: kube-system + labels: + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + app.kubernetes.io/managed-by: {{.Release.Service | quote }} + app.kubernetes.io/instance: {{.Release.Name | quote }} + helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" + annotations: + helm.sh/hook: pre-install + helm.sh/hook-delete-policy: hook-before-creation,hook-succeeded +spec: + template: + spec: + containers: + - name: labeler + image: gcr.io/google_containers/hyperkube:v1.9.7 + command: + - kubectl + - label + - --overwrite + - ns + - {{ .Release.Namespace }} + - istio-injection=enabled + restartPolicy: Never + # use tiller service account since it should have permissions to do namespace labeling + serviceAccountName: tiller diff --git a/charts/airdrop-service/templates/_helpers.tpl b/charts/airdrop-service/templates/_helpers.tpl new file mode 100644 index 0000000000000000000000000000000000000000..f2bd56ef6b1ce94be3dc9a554d71fab2e268f761 --- /dev/null +++ b/charts/airdrop-service/templates/_helpers.tpl @@ -0,0 +1,33 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "airdrop-service.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "airdrop-service.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "airdrop-service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + diff --git a/charts/airdrop-service/templates/cass-pass-secret.yaml b/charts/airdrop-service/templates/cass-pass-secret.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c88463f20471da69a72be235cf1a41d3d4cf61af --- /dev/null +++ b/charts/airdrop-service/templates/cass-pass-secret.yaml @@ -0,0 +1,14 @@ +{{- if .Values.cassandra.password }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "airdrop-service.fullname" . }}-casspwd + labels: + app: {{ template "airdrop-service.name" . }} + chart: {{ template "airdrop-service.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +type: Opaque +data: + password: {{ .Values.cassandra.password | b64enc | quote }} +{{- end }} diff --git a/charts/airdrop-service/templates/deployment.yaml b/charts/airdrop-service/templates/deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..70913f4b9a319409d64b8d0dc02eee9a7596aeb5 --- /dev/null +++ b/charts/airdrop-service/templates/deployment.yaml @@ -0,0 +1,125 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: {{ template "airdrop-service.fullname" . }} + labels: + app: {{ template "airdrop-service.name" . }} + chart: {{ template "airdrop-service.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + replicas: {{ .Values.replicaCount }} + template: + metadata: + labels: + app: {{ template "airdrop-service.name" . }} + release: {{ .Release.Name }} + spec: + containers: + - name: {{ template "airdrop-service.name" . }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + readinessProbe: + exec: + command: + - /bin/sh + - -c + - curl --silent http://localhost:{{ .Values.service.port }}/airdrop/actuator/health | grep UP + successThreshold: 1 + failureThreshold: 10 + initialDelaySeconds: 60 + periodSeconds: 5 + timeoutSeconds: 5 + livenessProbe: + httpGet: + path: /airdrop/actuator/info + port: {{ .Values.service.port }} + successThreshold: 1 + failureThreshold: 10 + initialDelaySeconds: 60 + periodSeconds: 5 + timeoutSeconds: 5 + resources: +{{ toYaml .Values.resources | indent 12 }} + env: + - name: SERVER_PORT + value: {{ .Values.service.port | quote }} + - name: CASSANDRA_PORT + value: {{ .Values.cassandra.port | quote }} + - name: CASSANDRA_KEYSPACE + value: {{ .Values.cassandra.keyspace | quote }} +{{- if .Values.cassandra.userName }} + - name: CASSANDRA_USERNAME + value: {{ .Values.cassandra.userName | quote }} + - name: CASSANDRA_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "airdrop-service.fullname" . }}-casspwd + key: password +{{- end }} + - name: CASSANDRA_CONTACTPOINTS + value: {{ .Values.cassandra.contactPoints | quote }} + - name: KAFKA_HOST + value: {{ .Values.kafka.host | quote }} + - name: KAFKA_PORT + value: {{ .Values.kafka.port | quote }} + - name: KAFKA_GROUP + value: {{ .Values.kafka.group | quote }} + - name: KAFKA_TOPIC + value: {{ .Values.kafka.topic | quote }} + - name: OTP_EXPIRETIME + value: {{ .Values.otp.expireTime | quote }} + - name: GRPC_ACCOUNTSERVICE_HOST + value: {{ .Values.grpc.accountService.host | quote }} + - name: GRPC_ACCOUNTSERVICE_PORT + value: {{ .Values.grpc.accountService.port | quote }} + - name: GRPC_AUTHSERVICE_HOST + value: {{ .Values.grpc.authService.host | quote }} + - name: GRPC_AUTHSERVICE_PORT + value: {{ .Values.grpc.authService.port | quote }} + - name: REST_PUSHSERVICE_ADDRESS + value: {{ .Values.rest.pushService.address | quote }} + - name: REST_QOINPRO_ADDRESS + value: {{ .Values.rest.qoinPro.address | quote }} + - name: LOGGING_LEVEL_ROOT + value: {{ .Values.logging.level.root | quote }} + - name: AUTHENTICATION_QOINPRO_HEADER_NAME + value: {{ .Values.authentication.qoinPro.header.name | quote }} + - name: AUTHENTICATION_QOINPRO_HEADER_VALUE + valueFrom: + secretKeyRef: + name: {{ template "airdrop-service.fullname" . }}-qoinpro-key + key: qoinPro +{{- if .Values.users.qoinPro.client }} + - name: USERS_QOINPRO_CLIENT + value: {{ .Values.users.qoinPro.client | quote }} + - name: USERS_QOINPRO_SECRET + valueFrom: + secretKeyRef: + name: {{ template "airdrop-service.fullname" . }}-qoinpro-user + key: secret +{{- end }} +{{- if .Values.users.qoinPro.userName }} + - name: USERS_QOINPRO_USERNAME + value: {{ .Values.users.qoinPro.userName | quote }} + - name: USERS_QOINPRO_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "airdrop-service.fullname" . }}-qoinpro-user + key: password +{{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} diff --git a/charts/airdrop-service/templates/qoinPro-header-secrets.yaml b/charts/airdrop-service/templates/qoinPro-header-secrets.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9da1a4aa400d7f8758d6986c2c4d612ade65c7d9 --- /dev/null +++ b/charts/airdrop-service/templates/qoinPro-header-secrets.yaml @@ -0,0 +1,14 @@ +{{- if .Values.authentication.qoinPro.header.value}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "airdrop-service.fullname" . }}-qoinpro-key + labels: + app: {{ template "airdrop-service.name" . }} + chart: {{ template "airdrop-service.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +type: Opaque +data: + qoinPro: {{ .Values.authentication.qoinPro.header.value | b64enc | quote }} +{{- end }} diff --git a/charts/airdrop-service/templates/qoinpro-users-secrets.yaml b/charts/airdrop-service/templates/qoinpro-users-secrets.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3df5b232db8b7ce25e203e58fde134ef2376a6ff --- /dev/null +++ b/charts/airdrop-service/templates/qoinpro-users-secrets.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "airdrop-service.fullname" . }}-qoinpro-user + labels: + app: {{ template "airdrop-service.name" . }} + chart: {{ template "airdrop-service.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +type: Opaque +data: + secret: {{ .Values.users.qoinPro.secret | b64enc | quote }} + password: {{ .Values.users.qoinPro.password | b64enc | quote }} \ No newline at end of file diff --git a/charts/airdrop-service/templates/service.yaml b/charts/airdrop-service/templates/service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..adbfc32862bfeba0f9624f7354055632dd3945a0 --- /dev/null +++ b/charts/airdrop-service/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "airdrop-service.fullname" . }} + labels: + app: {{ template "airdrop-service.name" . }} + chart: {{ template "airdrop-service.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + type: {{ .Values.service.type }} + selector: + app: {{ template "airdrop-service.name" . }} + release: {{ .Release.Name }} + ports: + - protocol: TCP + port: {{ .Values.service.port }} + targetPort: {{ .Values.service.port }} + name: http diff --git a/charts/airdrop-service/templates/virtualservice.yaml b/charts/airdrop-service/templates/virtualservice.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d689d8234e62b45c38082a863f240fc1992c74db --- /dev/null +++ b/charts/airdrop-service/templates/virtualservice.yaml @@ -0,0 +1,28 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: {{ template "airdrop-service.fullname" . }} + labels: + app: {{ template "airdrop-service.name" . }} + chart: {{ template "airdrop-service.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + gateways: + {{- range .Values.gateway.selector }} + - {{ . }} + {{- end }} + hosts: + {{- range .Values.gateway.hosts }} + - {{ . }} + {{- end }} + http: + - match: + - uri: + prefix: /airdrop + route: + - destination: + host: {{ template "airdrop-service.fullname" . }} + port: + number: {{ .Values.service.port }} + timeout: 30s \ No newline at end of file diff --git a/charts/airdrop-service/values.yaml b/charts/airdrop-service/values.yaml new file mode 100644 index 0000000000000000000000000000000000000000..56b903ab093de10253cb775397c9c5c373c595db --- /dev/null +++ b/charts/airdrop-service/values.yaml @@ -0,0 +1,99 @@ +replicaCount: 1 + +image: + repository: eu.gcr.io/nynja-ci-201610/airdrop/airdrop-service + tag: stable + pullPolicy: IfNotPresent + +gateway: + selector: + - api-gateway.default.svc.cluster.local + hosts: + - airdrop.dev-eu.nynja.net + +resources: + limits: + cpu: 200m + memory: 700Mi + requests: + cpu: 50m + memory: 500Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# SERVER PROPERTIES +service: + type: "ClusterIP" + port: 8080 + #grpc: 6565 + +# CASSANDRA PROPERTIES +cassandra: + port: 9042 + keyspace: "airdrop" + userName: null + password: null + # contactPoints: 127.0.0.1 + contactPoints: "cassandra.cassandra.svc.cluster.local" + +# KAFKA PROPERTIES +kafka: + host: "kafka.kafka.svc.cluster.local" + port: 9092 + group: airdrop + topic: events + +# OTP PROPERTIES +otp: + expireTime: 120 # Note : Expire Time is in seconds + + +# GRPC PROPERTIES +grpc: + accountService: + host: account.dev-eu.nynja.net + port: 6565 + authService: + host: auth.dev-eu.nynja.net + port: 6565 + + +# REST PROPERTIES +rest: + pushService: + address: "http://localhost:8082" + qoinPro: + address: "https://apis.qoinpro.com" + +# LOGGING PROPERTIES +logging: + level: + root: WARN + + +# QOIN PRO HEADER + +authentication: + qoinPro: + header: + name: 'x-api-key' + value: 'NEzzAZLl0Ma1Jos1bqqfk2TFHTywQ0Qo9IAFaO0y' + + +# QOIN-PRO TOKEN AUTHENTICATION DETAILS +users: + qoinPro: + client: qoinPro + secret: qoinProSecret + userName: qoinPro + password: qoinProPassword + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..7b966e570ab03b853d23c4a85d4c47e1e57dbfa0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,213 @@ + + + 4.0.0 + + Nynja + Airdrop + 1.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-web + 2.0.3.RELEASE + + + + org.springframework + spring-context + 5.1.3.RELEASE + + + + + org.springframework.security.oauth + spring-security-oauth2 + 2.3.4.RELEASE + + + + org.springframework.boot + spring-boot-starter-security + 2.1.3.RELEASE + + + + + org.springframework.data + spring-data-cassandra + 2.0.8.RELEASE + + + io.netty + netty-handler + + + + + + + + + + + io.micrometer + micrometer-registry-prometheus + 1.1.3 + + + + + + io.springfox + springfox-swagger-ui + 2.8.0 + + + + io.springfox + springfox-swagger2 + 2.9.2 + + + + + + + + org.springframework.kafka + spring-kafka + 2.2.6.RELEASE + + + + + io.grpc + grpc-netty + 1.21.0 + + + io.grpc + grpc-protobuf + 1.21.0 + + + io.grpc + grpc-stub + 1.21.0 + + + + io.github.lognet + grpc-spring-boot-starter + 3.0.0 + + + + + + + com.google.guava + guava + 23.6-jre + + + org.apache.httpcomponents + httpcore + 4.4.8 + + + + + + org.springframework.boot + spring-boot-starter-actuator + 2.0.3.RELEASE + + + + + + + kr.motd.maven + os-maven-plugin + 1.6.1 + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + + com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier} + + grpc-java + + io.grpc:protoc-gen-grpc-java:1.4.0:exe:${os.detected.classifier} + + + + + + compile + compile-custom + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.0.5.RELEASE + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + + java + + + + + com.example.grpc.server.MyGrpcServer + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + org.springframework.boot + spring-boot-maven-plugin + 2.0.5.RELEASE + + + + repackage + + + + + + + \ No newline at end of file diff --git a/releases/dev/airdrop-service.yaml b/releases/dev/airdrop-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c5edd8d79fa805343a9287ef6da35e75881ff246 --- /dev/null +++ b/releases/dev/airdrop-service.yaml @@ -0,0 +1,85 @@ +apiVersion: flux.weave.works/v1beta1 +kind: HelmRelease +metadata: + name: airdrop-service + namespace: airdrop +spec: + releaseName: airdrop-service + chart: + repository: https://nynjagroup.jfrog.io/nynjagroup/helm/ + name: airdrop-service + version: 0.1.2 + values: + replicaCount: 1 + image: + repository: ${IMAGE_NAME} + tag: ${IMAGE_BUILD_TAG} + pullPolicy: IfNotPresent + gateway: + selector: + - api-gateway.default.svc.cluster.local + hosts: + - airdrop.dev-eu.nynja.net + resources: + limits: + cpu: 200m + memory: 700Mi + requests: + cpu: 50m + memory: 500Mi + nodeSelector: {} + tolerations: [] + affinity: {} + # SERVER PROPERTIES + service: + type: "ClusterIP" + port: 8080 + #grpc: 6565 + # CASSANDRA PROPERTIES + cassandra: + port: 9042 + keyspace: "airdrop" + userName: null + password: null + # contactPoints: 127.0.0.1 + contactPoints: "cassandra.cassandra.svc.cluster.local" + # KAFKA PROPERTIES + kafka: + host: "kafka.kafka.svc.cluster.local" + port: 9092 + group: airdrop + topic: events + # OTP PROPERTIES + otp: + expireTime: 120 # Note : Expire Time is in seconds + # GRPC PROPERTIES + grpc: + accountService: + host: account.dev-eu.nynja.net + port: 6565 + authService: + host: auth.dev-eu.nynja.net + port: 6565 + # REST PROPERTIES + rest: + pushService: + address: "http://localhost:8082" + qoinPro: + address: "https://apis.qoinpro.com" + # LOGGING PROPERTIES + logging: + level: + root: WARN + # QOIN PRO HEADER + authentication: + qoinPro: + header: + name: "x-api-key" + value: "NEzzAZLl0Ma1Jos1bqqfk2TFHTywQ0Qo9IAFaO0y" + # QOIN-PRO TOKEN AUTHENTICATION DETAILS + users: + qoinPro: + client: "qoinPro" + secret: "qoinProSecret" + userName: "qoinPro" + password: "qoinProPassword" \ No newline at end of file diff --git a/releases/staging/airdrop-service.yaml b/releases/staging/airdrop-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c5edd8d79fa805343a9287ef6da35e75881ff246 --- /dev/null +++ b/releases/staging/airdrop-service.yaml @@ -0,0 +1,85 @@ +apiVersion: flux.weave.works/v1beta1 +kind: HelmRelease +metadata: + name: airdrop-service + namespace: airdrop +spec: + releaseName: airdrop-service + chart: + repository: https://nynjagroup.jfrog.io/nynjagroup/helm/ + name: airdrop-service + version: 0.1.2 + values: + replicaCount: 1 + image: + repository: ${IMAGE_NAME} + tag: ${IMAGE_BUILD_TAG} + pullPolicy: IfNotPresent + gateway: + selector: + - api-gateway.default.svc.cluster.local + hosts: + - airdrop.dev-eu.nynja.net + resources: + limits: + cpu: 200m + memory: 700Mi + requests: + cpu: 50m + memory: 500Mi + nodeSelector: {} + tolerations: [] + affinity: {} + # SERVER PROPERTIES + service: + type: "ClusterIP" + port: 8080 + #grpc: 6565 + # CASSANDRA PROPERTIES + cassandra: + port: 9042 + keyspace: "airdrop" + userName: null + password: null + # contactPoints: 127.0.0.1 + contactPoints: "cassandra.cassandra.svc.cluster.local" + # KAFKA PROPERTIES + kafka: + host: "kafka.kafka.svc.cluster.local" + port: 9092 + group: airdrop + topic: events + # OTP PROPERTIES + otp: + expireTime: 120 # Note : Expire Time is in seconds + # GRPC PROPERTIES + grpc: + accountService: + host: account.dev-eu.nynja.net + port: 6565 + authService: + host: auth.dev-eu.nynja.net + port: 6565 + # REST PROPERTIES + rest: + pushService: + address: "http://localhost:8082" + qoinPro: + address: "https://apis.qoinpro.com" + # LOGGING PROPERTIES + logging: + level: + root: WARN + # QOIN PRO HEADER + authentication: + qoinPro: + header: + name: "x-api-key" + value: "NEzzAZLl0Ma1Jos1bqqfk2TFHTywQ0Qo9IAFaO0y" + # QOIN-PRO TOKEN AUTHENTICATION DETAILS + users: + qoinPro: + client: "qoinPro" + secret: "qoinProSecret" + userName: "qoinPro" + password: "qoinProPassword" \ No newline at end of file diff --git a/src/main/java/biz/nynja/airdrop/Application.java b/src/main/java/biz/nynja/airdrop/Application.java new file mode 100644 index 0000000000000000000000000000000000000000..43945852c456e225e05c0ea5f5f3e213703534fd --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/Application.java @@ -0,0 +1,32 @@ +package biz.nynja.airdrop; + +import biz.nynja.airdrop.entity.OtpSessions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration; + +import java.util.*; + + +@SpringBootApplication(exclude = KafkaAutoConfiguration.class) +public class Application { + + private static final Logger logger = LoggerFactory.getLogger(Application.class); + + public static void main(String[] args) { + + SpringApplication.run(Application.class, args); + logger.info("Airdrop Service is started"); + + System.out.println(""); + System.out.println("-----------------"); + System.out.println(""); + System.out.println("AIR-DROP SERVICE IS STARTED"); + System.out.println(""); + System.out.println("-----------------"); + System.out.println(""); + + } +} diff --git a/src/main/java/biz/nynja/airdrop/CassandraStartupScripts.java b/src/main/java/biz/nynja/airdrop/CassandraStartupScripts.java new file mode 100644 index 0000000000000000000000000000000000000000..7760aae5df5f555b1d4c092c07f6b12d00597d70 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/CassandraStartupScripts.java @@ -0,0 +1,68 @@ +package biz.nynja.airdrop; + +import biz.nynja.airdrop.constants.Constants; +import biz.nynja.airdrop.entity.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.cassandra.core.CassandraTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; + +@Component +public class CassandraStartupScripts { + + @Value("${cassandra.keyspace}") + private String KEYSPACE; + + @Autowired + private CassandraTemplate cassandraTemplate; + + public static final Logger logger = LoggerFactory.getLogger(Config.class); + + @PostConstruct + public void cassandraScripts() { + + List cassandraScripts = new ArrayList(6); + + String createKeyspace = "CREATE KEYSPACE IF NOT EXISTS " + KEYSPACE + " WITH replication = {'class':'SimpleStrategy', 'replication_factor' : 1};"; + String createTableAirdropProfile = "CREATE TABLE IF NOT EXISTS " + KEYSPACE + "." + cassandraTemplate.getTableName(AirdropProfiles.class).toString() + " (id uuid, account_id uuid, username text, referrer_code text, created_ts bigint, modified_ts bigint, is_verified tinyint ,verified_ts bigint,PRIMARY KEY(account_id,id));"; + String createTableAirdropActions = "CREATE TABLE IF NOT EXISTS " + KEYSPACE + "." + cassandraTemplate.getTableName(AirdropActions.class).toString() + " (id uuid, account_id uuid, action_type text, last_action_ts bigint, count int, PRIMARY KEY(account_id,action_type,id));"; + String createTableOtpSessions = "CREATE TABLE IF NOT EXISTS " + KEYSPACE + "." + cassandraTemplate.getTableName(OtpSessions.class).toString() + " (id uuid, created_ts bigint, otp int, is_verified tinyint, verified_ts bigint, account_id uuid, expired_ts bigint, PRIMARY KEY(id));"; + String createTableActionsSummary = "CREATE TABLE IF NOT EXISTS " + KEYSPACE + "." + cassandraTemplate.getTableName(ActionsSummary.class).toString() + " (id uuid, action text, is_active tinyint, action_description text, PRIMARY KEY(action, id));"; + String createTableActionsFailed = "CREATE TABLE IF NOT EXISTS " + KEYSPACE + "." + cassandraTemplate.getTableName(ActionsFailed.class).toString() + " (id uuid, account_id uuid, username text, action_type text, action_detail text, created_ts bigint, channel text, PRIMARY KEY(id));"; + + String insertJoinInActionsSummary = "INSERT INTO actions_summary(id, action, is_active, action_description) values(db4930e0-9639-11e9-b82a-85b089918b4d, '" + Constants.KafkaActionType.JOIN + "', 1, '') IF NOT EXISTS"; + String insertContactInActionsSummary = "INSERT INTO actions_summary(id, action, is_active, action_description) values(f6829dd6-8db9-4c9a-9b5b-9809e020cab9, '" + Constants.KafkaActionType.CONTACT + "', 1, '') IF NOT EXISTS"; + String inserInviteNewContactInActionsSummary = "INSERT INTO actions_summary(id, action, is_active, action_description) values(089f9607-604f-40b9-a8f0-83367cd8d976, '" + Constants.KafkaActionType.INVITE_NEW_CONTACT + "', 1, '') IF NOT EXISTS"; + String insertDailySignInInActionsSummary = "INSERT INTO actions_summary(id, action, is_active, action_description) values(99b30c26-2198-4ba1-a951-3c26241c216f, '" + Constants.KafkaActionType.DAILY_SIGIN + "', 1, '') IF NOT EXISTS"; + String insertUse100DaysInActionsSummary = "INSERT INTO actions_summary(id, action, is_active, action_description) values(868ac214-804e-4297-bc97-bbca410667e1, '" + Constants.KafkaActionType.USE_100_DAYS + "', 1, '') IF NOT EXISTS"; + + cassandraScripts.add(createKeyspace); + cassandraScripts.add(createTableAirdropProfile); + cassandraScripts.add(createTableAirdropActions); + cassandraScripts.add(createTableOtpSessions); + cassandraScripts.add(createTableActionsSummary); + cassandraScripts.add(createTableActionsFailed); + + cassandraScripts.add(insertJoinInActionsSummary); + cassandraScripts.add(insertContactInActionsSummary); + cassandraScripts.add(inserInviteNewContactInActionsSummary); + cassandraScripts.add(insertDailySignInInActionsSummary); + cassandraScripts.add(insertUse100DaysInActionsSummary); + + try { + for (String cassandraScript : cassandraScripts) { + cassandraTemplate.getCqlOperations().execute(cassandraScript); + } + } catch (Exception e) { + logger.warn("Exception in executing cassandra scripts, Message: {}, Cause: {}", e.getMessage(), e.getCause()); + e.getStackTrace(); + } + } + +} diff --git a/src/main/java/biz/nynja/airdrop/Config.java b/src/main/java/biz/nynja/airdrop/Config.java new file mode 100644 index 0000000000000000000000000000000000000000..d6bdd662c6bb1fe0442d119a132f1a58c7679fed --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/Config.java @@ -0,0 +1,313 @@ +package biz.nynja.airdrop; + +import biz.nynja.airdrop.kafka.KafkaMessageTemplate; +import com.datastax.driver.core.Cluster; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.micrometer.core.instrument.MeterRegistry; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.cassandra.config.CassandraSessionFactoryBean; +import org.springframework.data.cassandra.config.SchemaAction; +import org.springframework.data.cassandra.core.CassandraOperations; +import org.springframework.data.cassandra.core.CassandraTemplate; +import org.springframework.data.cassandra.core.convert.CassandraConverter; +import org.springframework.data.cassandra.core.convert.MappingCassandraConverter; +import org.springframework.data.cassandra.core.mapping.CassandraMappingContext; +import org.springframework.kafka.annotation.EnableKafka; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.core.*; +import org.springframework.kafka.listener.ContainerProperties; +import org.springframework.kafka.support.serializer.JsonDeserializer; +import org.springframework.kafka.support.serializer.JsonSerializer; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +@ComponentScan(basePackages = "biz.nynja.airdrop.*") +@EnableKafka +// @EnableSwagger2 +public class Config implements WebMvcConfigurer { + + //======= CASSANDRA PROPERTIES =========== + @Value("${cassandra.keyspace}") + private String KEYSPACE; + @Value("${cassandra.userName}") + private String USER_NAME; + @Value("${cassandra.password}") + private String PASSWORD; + @Value("${cassandra.contactPoints}") + private String CONTACT_POINTS; + @Value("${cassandra.port}") + private int PORT; + + //======= CASSANDRA PROPERTIES =========== + @Value("${kafka.host}") + private String KAFKA_HOST; + @Value("${kafka.port}") + private String KAFKA_PORT; + @Value("${kafka.group}") + private String KAFKA_GROUP; + @Value("${kafka.topic}") + private String KAFKA_TOPIC; + + //======= GRPC PROPERTIES =========== + @Value("${grpc.accountService.host}") + private String GRPC_ACCOUNT_SERVICE_HOST; + @Value("${grpc.accountService.port}") + private int GRPC_ACCOUNT_SERVICE_PORT; + + @Value("${grpc.authService.host}") + private String GRPC_AUTH_SERVICE_HOST; + @Value("${grpc.authService.port}") + private int GRPC_AUTH_SERVICE_PORT; + + + //======= SWAGGER PROPERTIES ======== + @Value("${swagger.apiVersion}") + private String API_VERSION; + @Value("${swagger.licenseText}") + private String LICENSE_TEXT; + @Value("${swagger.title}") + private String TITLE; + @Value("${swagger.description}") + private String DESCRIPTION; + + + public static final Logger logger = LoggerFactory.getLogger(Config.class); + + //======= CASSANDRA CONFIGURATION =========== + + @Bean + protected Cluster cluster() { + + Cluster cluster = Cluster.builder().addContactPoint(CONTACT_POINTS).withPort(PORT).withCredentials(USER_NAME, PASSWORD).build(); + return cluster; + } + + @Bean + protected CassandraMappingContext mappingContext() { + + return new CassandraMappingContext(); + } + + @Bean + protected CassandraConverter converter() { + + return new MappingCassandraConverter(mappingContext()); + } + + @Bean + protected CassandraSessionFactoryBean cassandraSession() { + + CassandraSessionFactoryBean session = new CassandraSessionFactoryBean(); + + try { + + + session.setCluster(cluster()); + session.setKeyspaceName(KEYSPACE); + session.setConverter(converter()); + session.setSchemaAction(SchemaAction.NONE); + + System.out.println(""); + System.out.println("============"); + System.out.println(""); + System.out.println("CASSANDRA CONNECTION ESTABLISHING"); + System.out.println(""); + System.out.println("============"); + System.out.println(""); + + + logger.info("Cassandra connection is established"); + + } catch (Exception execption) { + + System.out.println(""); + System.out.println("----------"); + System.out.println(""); + System.out.println("EXCEPTION OCCURED IN CASSANDRA CONNECTION ESTABLISHMENT"); + System.out.println(""); + System.out.println(execption.getMessage()); + System.out.println(""); + System.out.println("----------"); + System.out.println(""); + + logger.error("Error in establishing cassandra connection. Message: {}, Cause: {}", execption.getMessage(), + execption.getCause()); + } + + return session; + + } + + @Bean + protected CassandraOperations cassandraTemplate() { + return new CassandraTemplate(cassandraSession().getObject()); + + } + + //======= CASSANDRA CONFIGURATION OVER =========== + + + //======= KAFKA PRODUCER CONFIGURATION =========== + + @Bean + public ProducerFactory producerFactory() { + + Map configProps = new HashMap(3); + + configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_HOST + ":" + KAFKA_PORT); + configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + // return new DefaultKafkaProducerFactory<>(configProps, new StringSerializer(), new JsonSerializer<>(User.class)); + + return new DefaultKafkaProducerFactory(configProps); + + } + + + @Bean + public KafkaTemplate kafkaTemplate() { + logger.info("Kafka producer setup is done"); + return new KafkaTemplate(producerFactory()); + } + + + //====== KAFKA PRODUCER CONFIGURATION OVER ======== + + + //======= KAFKA CONSUMER CONFIGURATION =========== + + @Bean + public ConsumerFactory consumerFactory() { + + Map config = new HashMap(6); + + config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KAFKA_HOST + ":" + KAFKA_PORT); + config.put(ConsumerConfig.GROUP_ID_CONFIG, KAFKA_GROUP); + config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class); + config.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + config.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); + // config.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1"); // Time(in seconds) after Auto commit will take place + logger.info("Kafka consumer setup is done"); + return new DefaultKafkaConsumerFactory(config, new StringDeserializer(), new org.springframework.kafka.support.serializer.JsonDeserializer(KafkaMessageTemplate.class)); + + } + + + @Bean + public ConcurrentKafkaListenerContainerFactory kafkaListenerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory(); + factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL); + factory.setConsumerFactory(consumerFactory()); + logger.info("Kafka message Acknowledge mode is set to: " + ContainerProperties.AckMode.MANUAL); + return factory; + } + + //====== KAFKA CONSUMER CONFIGURATION OVER ======== + + + //====== KAFKA GROUP ====== + @Bean(name = "kafkaGroup") + public String kafkaGroup() { + return KAFKA_GROUP; + } + + //====== KAFKA TOPIC ====== + @Bean(name = "kafkaTopic") + public String kafkaTopic() { + return KAFKA_TOPIC; + } + + + // ====== GRPC CONFIGURATION ===== + + @Bean(name = "accountServiceManagedChannel") + public ManagedChannel accountServiceManagedChannel() { + ManagedChannel managedChannel = ManagedChannelBuilder.forAddress(GRPC_ACCOUNT_SERVICE_HOST, GRPC_ACCOUNT_SERVICE_PORT).usePlaintext().build(); + logger.info("Account Service grpc channel is established"); + return managedChannel; + } + + @Bean(name = "authServiceManagedChannel") + public ManagedChannel authServiceManagedChannel() { + ManagedChannel managedChannel = ManagedChannelBuilder.forAddress(GRPC_AUTH_SERVICE_HOST, GRPC_AUTH_SERVICE_PORT).usePlaintext().build(); + logger.info("Auth Service grpc channel is established"); + return managedChannel; + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + + //======= SWAGGER CONFIGURATION ======== + +/* + protected ApiInfo apiInfo() { + + return new ApiInfoBuilder().license(LICENSE_TEXT).description(DESCRIPTION) + .version(API_VERSION).title(TITLE).build(); + } + + @Bean + protected Docket docket() { + + return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()) + //.pathMapping("/") + .select() + .apis(RequestHandlerSelectors.any()) + .paths(PathSelectors.any()) + .build(); + } +*/ + + //======= MICROMETER CONFIGURATION ======== + @Bean + public MeterRegistryCustomizer meterRegistryCustomizer(final MeterRegistry meterRegistry) { + return new MeterRegistryCustomizer() { + public void customize(MeterRegistry meterRegistry1) { + meterRegistry.config().commonTags("application", "AirdropService"); + } + }; + + } + + // ========= PASSWORD ENCODER ======== + @Bean + protected BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + + // =========== INTERCEPTOR CCONFIGURATION ====== + /*@Autowired + private KeyInterceptor keyInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + + registry.addInterceptor(keyInterceptor).addPathPatterns(InterceptorAPIs.apiWithKeyValidation); + } +*/ + +} + + diff --git a/src/main/java/biz/nynja/airdrop/constants/Constants.java b/src/main/java/biz/nynja/airdrop/constants/Constants.java new file mode 100644 index 0000000000000000000000000000000000000000..df4c592d8b713603891775d446035d1ba778af3d --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/constants/Constants.java @@ -0,0 +1,57 @@ +package biz.nynja.airdrop.constants; + +public interface Constants { + + interface ActionType { + // "contact", "sms", "videocall" + String CONTACT = "contact"; + String SMS = "contact"; + String VIDEOCALL = "videocall"; + } + + interface QoinProChannel { + + String NYNJA = "nynja"; + } + + + interface VerificationStatus { + short VERIFIED = 1; + short NOT_VERIFIED = 0; + } + + interface KafkaActionType { + String JOIN = "join"; + String USE_100_DAYS = "use100days"; + String DAILY_SIGIN = "dailysignin"; + String INVITE_NEW_CONTACT = "invitenewcontact"; + String CONTACT = "contact"; + } + + interface KafkaActionTypeActivationStatus { + short ACTIVE = 1; + short UNACTIVE = 0; + } + + interface ReferrerCode { + String QOINPRO = "qoinpro"; + } + + interface Swagger { + + interface ParamTypes { + String HEADER = "header"; + String BODY = "body"; + String FORM = "form"; + String QUERY = "query"; + String PATH = "path"; + } + + interface DataTypes { + String STRING = "string"; + String UUID = "uuid"; + String INTEGER = "int"; + String LONG = "long"; + } + } +} diff --git a/src/main/java/biz/nynja/airdrop/constants/InterceptorAPIs.java b/src/main/java/biz/nynja/airdrop/constants/InterceptorAPIs.java new file mode 100644 index 0000000000000000000000000000000000000000..653a35217af726e8ad2f4909077cebdb164b3b13 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/constants/InterceptorAPIs.java @@ -0,0 +1,6 @@ +package biz.nynja.airdrop.constants; + +/*public interface InterceptorAPIs { + + String [] apiWithKeyValidation = {"/v1/verifyUser", "/v1/verifyOtp", "/v1/resendOtp", "/v1/eventData"}; +}*/ diff --git a/src/main/java/biz/nynja/airdrop/constants/QoinProEndpoints.java b/src/main/java/biz/nynja/airdrop/constants/QoinProEndpoints.java new file mode 100644 index 0000000000000000000000000000000000000000..130b053a2e7894c3e6f959315dee80efa15a54d8 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/constants/QoinProEndpoints.java @@ -0,0 +1,6 @@ +package biz.nynja.airdrop.constants; + +public interface QoinProEndpoints { + + String qoinProEndPoints [] = {"/test11", "/v1/verifyUser", "/v1/verifyOtp", "/v1/resendOtp", "/v1/eventData", "/v1/failedEventData"}; +} diff --git a/src/main/java/biz/nynja/airdrop/constants/ServiceAPIs.java b/src/main/java/biz/nynja/airdrop/constants/ServiceAPIs.java new file mode 100644 index 0000000000000000000000000000000000000000..b52b90bf95505f89e83567a2ba1ca46460e17a12 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/constants/ServiceAPIs.java @@ -0,0 +1,14 @@ +package biz.nynja.airdrop.constants; + + +public interface ServiceAPIs { + + interface qoinPro { + String channelEngagement = "/v1/channel-engagement"; + } + + interface pushService { + String pushNotification = "/dummy/pushNotification"; + } + +} diff --git a/src/main/java/biz/nynja/airdrop/controller/AirdropController.java b/src/main/java/biz/nynja/airdrop/controller/AirdropController.java new file mode 100644 index 0000000000000000000000000000000000000000..5f479cd88c75c309bf49a9200ff4408312b2d380 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/controller/AirdropController.java @@ -0,0 +1,1022 @@ +package biz.nynja.airdrop.controller; + +import biz.nynja.airdrop.constants.Constants; +import biz.nynja.airdrop.entity.ActionsFailed; +import biz.nynja.airdrop.entity.AirdropActions; +import biz.nynja.airdrop.entity.AirdropProfiles; +import biz.nynja.airdrop.entity.OtpSessions; +import biz.nynja.airdrop.grpc.AccountServiceClient; +import biz.nynja.airdrop.grpc.AuthServiceClient; +import biz.nynja.airdrop.kafka.KafkaMessageTemplate; +import biz.nynja.airdrop.kafka.KafkaProducer; +import biz.nynja.airdrop.service.*; +import biz.nynja.airdrop.util.AirdropUtil; +import biz.nynja.airdrop.util.HttpConstants; +import biz.nynja.airdrop.util.HttpResponse; +import biz.nynja.airdrop.util.RestTemplateUtil; +import io.micrometer.core.annotation.Timed; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; + +import static biz.nynja.airdrop.util.HttpConstants.statusCode.*; +import static biz.nynja.airdrop.util.HttpConstants.MessageTags.*; + +import java.io.IOException; +import java.util.*; + +/** + * This Controller is for integration of Nynja Users with QuionPro + * QuionPro will use this Controller for user verification through + * Nynja Account Service using OTP. And maintaining user + * actions such as "join". + * + * @author Jayendra + * @version 0.1 + * @package com.airdrop.controller + * @link http:///airdrop + * @since 2019-06-18 + */ + + +@RestController +// @Api("It contains APIs to verify User and fetch user Action Details") +public class AirdropController { + + @Autowired + private AirdropProfilesService airdropProfilesService; + + @Autowired + private AirdropActionsService airdropActionsService; + + @Autowired + private OtpSessionsService otpSessionsService; + + @Autowired + private ActionSummaryService actionSummaryService; + + @Autowired + private KafkaProducer kafkaProducer; + + @Autowired + private AccountServiceClient accountServiceClient; + + @Autowired + private AuthServiceClient authServiceClient; + + @Autowired + private RestTemplateUtil restTemplateUtil; + + @Autowired + private ActionsFailedService actionsFailedService; + + @Value("${otp.expireTime}") + private long OTP_EXPIRE_TIME; + + @Autowired + @Qualifier("kafkaGroup") + private String kafkaGroup; + + @Autowired + @Qualifier("kafkaTopic") + private String kafkaTopic; + + private static final Logger logger = LoggerFactory.getLogger(AirdropController.class); + + @GetMapping("/") + public String index() { + + return "Airdrop Service"; + } + + + @GetMapping("/test11") + public String test11() { + + return "Test Endpoint WITH Token"; + } + + @GetMapping("/test12") + public String test12() { + + return "Test Endpoint WITH-OUT Token"; + } + + + @GetMapping("/test/verifyAccount/{username}") + public String test1(@PathVariable("username") String username) { + + System.out.println(""); + System.out.println("--------------"); + System.out.println(""); + System.out.println("Testing verifyAccount Endpoint"); + System.out.println(""); + System.out.println("username : " + username); + System.out.println("AccessToken : " + authServiceClient.tokens.get("accessToken")); + System.out.println(""); + System.out.println("--------------"); + System.out.println(""); + + + UUID accountId = accountServiceClient.verifyAccount(username, authServiceClient.tokens.get("accessToken")); + + return "VerifyAccount-Response : " + accountId; + } + + + @GetMapping("/test/exchangeToken") + public String test2() throws IOException { + + System.out.println(""); + System.out.println("--------------"); + System.out.println(""); + System.out.println("Testing Exchange-Token Endpoint"); + System.out.println(""); + System.out.println("--------------"); + System.out.println(""); + + boolean response = authServiceClient.exchangeRefreshToken(); + + return "ExchangeToken-Response : " + response; + } + + + + @GetMapping("/test/exchangeToken/refreshToken/{refreshToken}") + public String test3(@PathVariable("refreshToken") String refreshToken) throws IOException { + + System.out.println(""); + System.out.println("--------------"); + System.out.println(""); + System.out.println("Testing Exchange-Token Endpoint By Manual Refresh-Token"); + System.out.println(""); + System.out.println("GivenRefreshToken : " + refreshToken); + System.out.println(""); + System.out.println("--------------"); + System.out.println(""); + + boolean response = authServiceClient.exchangeRefreshTokenManual(refreshToken); + + return "ExchangeTokenManual-Response : " + response; + } + + + /** + * Function verifies user by username through Nynja Account Service. + * If user is verified than OTP Session of user will be created. + * And Nynja Push Service will be called to Send OTP on user's Device + * + * @param "username" type="string username in Nynja Account Service + * @return "status" : 200/400, "data" : {"is_verified": 0/1, "otp_session_id": , "expiry_duration" : } + * @link http:///airdrop/v1/verifyUser + * @version 0.1 + * @produces application/json + **/ + + @PostMapping(value = "/v1/verifyUser", produces = "application/json") + @Timed(value = "verifyUser.request", histogram = true, extraTags = {"version", "0.1"}) + /* @ApiOperation(value = "This API will verify User from Nynja Account Service" + + "and if user is verified than that user will be registered to Airdrop database and OTP will be sent to" + + "user mobile by calling Nynja Push Service" + ) + @ApiImplicitParams({ + @ApiImplicitParam(name = "username", required = true, dataType = STRING, paramType = QUERY, value = "Username")}) + @ApiResponses(value = {@ApiResponse(code = 200, message = "In response their will user Verification Status, otp session id" + + "and expiry duration of otp in seconds\n\n\n" + + "\n" + + "{\n" + + " \"data\": {\n" + + " \"is_verified\": 0,\n" + + " \"otp_session_id\": \"\",\n" + + " \"expiry_duration\": \n" + + " },\n" + + " \"status\": 200\n" + + "}" + )})*/ + public Map verifyUser(@RequestParam("username") String username) { + + logger.info("verifyUser API is called"); + logger.info("Parameters:-"); + logger.info("username: " + username); + + + /*System.out.println(""); + System.out.println("----------------"); + System.out.println(""); + System.out.println("VERIFY USER CONTROLLER"); + System.out.println(""); + System.out.println("username : " + username); + System.out.println(""); + System.out.println("----------------"); + System.out.println("");*/ + + + if (username.equals("")) { + + /*System.out.println(""); + System.out.println("Username is blank"); + System.out.println("");*/ + + logger.warn("Username is blank"); + + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, null, USERNAME_IS_BLANK); + } + + /* + + // Here, Nynja Account Service will be called to Verify UserName + + */ + + // NOTE : This accountId will be fetched from Nynja Account Service + // NOTE : If user is not verified through account service then response will be: "is_verified" : 0 + + // This code is Dummy + System.out.println(""); + System.out.println("AccessToken : " + authServiceClient.tokens.get("accessToken")); + System.out.println(""); + UUID accountId = accountServiceClient.verifyAccount(username, authServiceClient.tokens.get("accessToken") ); + + logger.info("accountId:"); + logger.debug("{}", accountId); + /*System.out.println(""); + System.out.println("accountId : " + accountId); + System.out.println("");*/ + + if (accountId == null) { + + /* System.out.println(""); + System.out.println("Account Service Validation Failed. No User with username : " + username); + System.out.println("");*/ + + logger.warn("Account Service Validation Failed. No User with username : " + username); + + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, null, HttpConstants.MessageTags.AIRDROP_PROFILE_NOT_EXISTS); + + + } + + // NOTE :- This code commented and will be removed in future because accoundID will be given by NynjaAccountService + // AirdropProfiles airdropProfile = airdropProfilesService.getAirdropProfileByUserName(username); + + List airdropProfilesList = airdropProfilesService.getAirdropProfiles(accountId, null); + + + /*System.out.println(""); + System.out.println("Airdrop Profile List"); + System.out.println(airdropProfilesList); + System.out.println("");*/ + + logger.info("Airdrop Profile List:"); + logger.debug("{}", airdropProfilesList); + + + if (!airdropProfilesList.isEmpty()) { + + AirdropProfiles airdropProfiles = airdropProfilesList.get(0); + + /*System.out.println(""); + System.out.println("Profile already exists"); + System.out.println(""); + System.out.println("airdropProfiles :"); + System.out.println(airdropProfiles); + System.out.println("");*/ + + logger.warn("Profile already exists"); + logger.warn("airdropProfiles: "); + logger.warn(airdropProfiles.toString()); + + if (airdropProfiles.getIs_verified() == Constants.VerificationStatus.VERIFIED) { + + /*System.out.println(""); + System.out.println("Airdrop Profile is already Verified"); + System.out.println("");*/ + + logger.warn("Airdrop Profile is already Verified"); + + Map responseData = new HashMap(1); + responseData.put("is_verified", (short) 1); + + return HttpResponse.getResponse(SUCCESS_STATUS_CODE, responseData, AIRDROP_PROFILE_ALREADY_VERIFIED); + } + + + } + + if (airdropProfilesList == null || airdropProfilesList.isEmpty()) { + + /*System.out.println(""); + System.out.println("Adding New Airbase Profile");*/ + + logger.info("Adding New Airbase Profile"); + + AirdropProfiles airdropProfilesObj = new AirdropProfiles(); + airdropProfilesObj.setId(UUID.randomUUID()); + airdropProfilesObj.setAccount_id(accountId); + airdropProfilesObj.setUsername(username); + airdropProfilesObj.setIs_verified(Constants.VerificationStatus.NOT_VERIFIED); + airdropProfilesObj.setCreated_ts(AirdropUtil.generateCurrentEpochTimeStamp()); + airdropProfilesObj.setModified_ts(0L); + airdropProfilesObj.setVerified_ts(0L); + airdropProfilesObj.setReferrer_code(Constants.ReferrerCode.QOINPRO); + + airdropProfilesService.addAirdropProfile(airdropProfilesObj); + + /*System.out.println(""); + System.out.println("New Airbase Profile is Added");*/ + logger.info("New Airbase Profile is Added"); + } + + int otp = AirdropUtil.generateOtpNumber(); + +/* System.out.println(""); + System.out.println("OTP Generated : " + otp); + System.out.println("");*/ + + logger.info("OTP generated: " + otp); + + UUID otpSessionId = UUID.randomUUID(); + + long epochCreationTimeStamp = AirdropUtil.generateCurrentEpochTimeStamp(); + + OtpSessions otpSessions = new OtpSessions(); + otpSessions.setAccount_id(accountId); + otpSessions.setId(otpSessionId); + otpSessions.setCreated_ts(epochCreationTimeStamp); + otpSessions.setOtp(otp); + otpSessions.setIs_verified(Constants.VerificationStatus.NOT_VERIFIED); + otpSessions.setVerified_ts(0L); + otpSessions.setExpired_ts((epochCreationTimeStamp + OTP_EXPIRE_TIME)); + + otpSessionsService.addOtpSessions(otpSessions); + + Map responseData = new HashMap(2); + responseData.put("is_verified", (short) 0); + responseData.put("otp_session_id", otpSessionId); + responseData.put("expiry_duration", OTP_EXPIRE_TIME); + + /* + + // HERE, NYNJA PUSH SERVICE WILL BE CALLED TO SEND NOTIFICATION TO USER + + */ + + restTemplateUtil.sendPushNotification(accountId.toString(), otp); + + return HttpResponse.getResponse(SUCCESS_STATUS_CODE, responseData, null); + } + + + /** + * Function verifies user by username through Nynja Account Service. + * If user is verified than than further validation is performed + * And OTP is verified by otp and otp_session_id + * And Nynja Push Service will be called to Send OTP on user's Device + * + * @param "username" type="string" username in Nynja Account Service + * @param "otp" type="string" otp + * @param "session_id" type="UUID" otp session id + * @return "status" : 200/400, "data" : {"is_verified": 0/1} + * @link http:///airdrop/v1/verifyOtp + * @version 0.1 + * @produces application/json + */ + + /* @ApiOperation(value = "This API will verify Airdrop Profile by OTP which will be entered by user. And after that Signed Action will" + + "be pushed to Kafka and than Kafka consumer will further call QuionPro Action API to notify user join Action.") + @ApiImplicitParams({ + @ApiImplicitParam(name = "username", required = true, dataType = STRING, paramType = QUERY, value = "Username"), + @ApiImplicitParam(name = "otp", required = true, dataType = INTEGER, paramType = QUERY, value = "Otp"), + @ApiImplicitParam(name = "session_id", required = true, dataType = STRING, paramType = QUERY, value = "OTP SessionId") + + }) + @ApiResponses(value = {@ApiResponse(code = 200, message = "In response their will be verification status of user\n" + + "{\n" + + " \"data\": {\n" + + " \"is_verified\": 1\n" + + " },\n" + + " \"status\": 200\n" + + "}" + )})*/ + @PostMapping(value = "/v1/verifyOtp", produces = "application/json") + @Timed(value = "verifyOtp.request", histogram = true, extraTags = {"version", "0.1"}) + public Map verifyOtp( + @RequestParam("username") String username, + @RequestParam("otp") String otpString, + @RequestParam("session_id") UUID sessionId // OTP SESSION-ID + ) { + + /*System.out.println(""); + System.out.println("-------------------"); + System.out.println(""); + System.out.println("VERIFY OTP CONTROLLER"); + System.out.println(""); + System.out.println("username : " + username); + System.out.println("otp : " + otpString); + System.out.println("sessionId : " + sessionId); + System.out.println(""); + System.out.println("-------------------"); + System.out.println("");*/ + + logger.info("Verify Otp API"); + logger.info("Parameters: "); + logger.info("username: " + username); + logger.info("otp: " + otpString); + logger.info("sessionId: " + sessionId); + + + int otp = 0; + try { + otp = Integer.valueOf(otpString); + } catch (NumberFormatException exception) { + + /*System.out.println(""); + System.out.println("OTP is not valid"); + System.out.println(""); + System.out.println("Exception : " + exception.getMessage()); + System.out.println("");*/ + + logger.warn("OTP is not valid"); + logger.warn("Exception in reading otp, message:{}, cause:{}", exception.getMessage(), exception.getCause()); + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, null, OTP_IS_NOT_VALID); + } + + + if (username.equals("")) { + + /*System.out.println(""); + System.out.println("Username is blank"); + System.out.println("");*/ + + logger.warn("username is blank"); + + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, null, USERNAME_IS_BLANK); + } + + /* + + // Here, Nynja Account Service will be called to Verify UserName + + */ + + // NOTE : This accountId will be fetched from Nynja Account Service + // NOTE : If user is not verified through account service then response will be: "is_verified" : 0 + + System.out.println(""); + System.out.println("AccessToken : " + authServiceClient.tokens.get("accessToken")); + System.out.println(""); + + // This code is Dummy + UUID accountId = accountServiceClient.verifyAccount(username, authServiceClient.tokens.get("accessToken")); + + if (accountId == null) { + +/* + System.out.println(""); + System.out.println("Account Service Validation Failed. No User with username : " + username); + System.out.println(""); +*/ + + logger.warn("Account Service validation failed. No user with username: " + username); + + Map responseData = new HashMap(1); + responseData.put("is_verified", Constants.VerificationStatus.NOT_VERIFIED); + + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, responseData, HttpConstants.MessageTags.AIRDROP_PROFILE_NOT_EXISTS); + + + } + + // Get accountId from otpSessions no need of username + List airdropProfilesList = airdropProfilesService.getAirdropProfiles(accountId, null); + + if (airdropProfilesList.isEmpty()) { + + /*System.out.println(""); + System.out.println("No, such Airdrop Profile exists"); + System.out.println("");*/ + + logger.warn("No, such Airdrop Profile exists"); + + Map responseData = new HashMap(1); + responseData.put("is_verified", Constants.VerificationStatus.NOT_VERIFIED); + + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, responseData, HttpConstants.MessageTags.AIRDROP_PROFILE_NOT_EXISTS); + } + + + AirdropProfiles airdropProfiles = airdropProfilesList.get(0); + + /*System.out.println(""); + System.out.println("Airdrop Profile"); + System.out.println(airdropProfiles); + System.out.println("");*/ + + + OtpSessions otpSession = otpSessionsService.getOtpSessions(sessionId); + + /*System.out.println(""); + System.out.println("otpSession"); + System.out.println(otpSession); + System.out.println("");*/ + + if (otpSession == null) { + + /* System.out.println(""); + System.out.println("OTP Session doesn't exists"); + System.out.println("");*/ + + logger.info("Otp session doesn't exists"); + + Map responseData = new HashMap(1); + responseData.put("is_verified", Constants.VerificationStatus.NOT_VERIFIED); + + // is_verified = 0 + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, responseData, OTP_SESSION_NOT_EXISTS); + + } else if (!airdropProfiles.getAccount_id().equals(otpSession.getAccount_id())) { + + /*System.out.println(""); + System.out.println("AccountId of OTPSession and Airdrop Profile doesn't matches"); + System.out.println("");*/ + + logger.warn("AccountId of OTPSession and Airdrop Profile doesn't matches"); + + Map responseData = new HashMap(1); + responseData.put("is_verified", Constants.VerificationStatus.NOT_VERIFIED); + + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, responseData, INVALID_OTP_SESSION); + + } else if (otpSession.getOtp() != otp) { + + /*System.out.println(""); + System.out.println("OTP not matched"); + System.out.println("");*/ + + logger.warn("OTP is not matched"); + + Map responseData = new HashMap(1); + responseData.put("is_verified", Constants.VerificationStatus.NOT_VERIFIED); + + + // is_verified = 0 + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, responseData, HttpConstants.MessageTags.OTP_NOT_MATCHED); + } else if (otpSession.getExpired_ts() < AirdropUtil.generateCurrentEpochTimeStamp()) { + + /*System.out.println(""); + System.out.println("OTP is Expired"); + System.out.println("");*/ + + logger.warn("OTP is not matched"); + + Map responseData = new HashMap(1); + responseData.put("is_verified", Constants.VerificationStatus.NOT_VERIFIED); + + + // is_verified = 0 + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, responseData, OTP_EXPIRED); + + } else if (airdropProfiles.getIs_verified() == Constants.VerificationStatus.VERIFIED) { + + // Check If already verified in Account + + /*System.out.println(""); + System.out.println("Airdrop profile is already verified"); + System.out.println("");*/ + + logger.info("Airdrop profile is already verified"); + + Map responseData = new HashMap(1); + responseData.put("is_verified", Constants.VerificationStatus.VERIFIED); + + return HttpResponse.getResponse(SUCCESS_STATUS_CODE, responseData, AIRDROP_PROFILE_ALREADY_VERIFIED); + + } + + Map updationMap = new HashMap(1); + updationMap.put("is_verified", Constants.VerificationStatus.VERIFIED); + updationMap.put("verified_ts", AirdropUtil.generateCurrentEpochTimeStamp()); + + boolean otpSessionUpdate = otpSessionsService.updateOtpSessions(sessionId, airdropProfiles.getAccount_id(), updationMap); + + updationMap.put("modified_ts", AirdropUtil.generateCurrentEpochTimeStamp()); + boolean airdropProfileUpdate = airdropProfilesService.updateAirdropProfile(airdropProfiles.getId(), airdropProfiles.getAccount_id(), updationMap); + + /*System.out.println(""); + System.out.println("otpSessionUpdate : " + otpSessionUpdate); + System.out.println("airdropProfileUpdate : " + airdropProfileUpdate); + System.out.println("");*/ + + logger.info("Airdrop profile is already verified"); + + KafkaMessageTemplate kafkaMessageTemplate = new KafkaMessageTemplate(); + kafkaMessageTemplate.setAccount_id(airdropProfiles.getAccount_id().toString()); + kafkaMessageTemplate.setAction_type(Constants.KafkaActionType.JOIN); + + /* System.out.println(""); + System.out.println("Message Pushed to Kafka Producer"); + System.out.println("");*/ + + logger.info("Message is pushed to kafka"); + kafkaProducer.sendKafkaMessage(kafkaMessageTemplate); + + Map responseData = updationMap; + responseData.remove("verified_ts"); + responseData.remove("modified_ts"); + + return HttpResponse.getResponse(SUCCESS_STATUS_CODE, responseData, null); + + } + + + /** + * Function sends new OTP on basis of last otp session id. + * First validation on otp session is going to be performed. + * if all validation passes than new OTP will be created nad + * Push Notification of Nynja will be called + * + * @param "session_id" type="UUID" otp session id + * @return "status" : 200/400, "data" : {"otp_session_id": , "expiry_duration" : } + * @link http:///airdrop/v1/resendOtp + * @version 0.1 + * @produces application/json + */ +/* + @ApiOperation(value = "This API will resend OTP by creating new OTP Session and calling Nynja Push Service to Send OTP") + @ApiImplicitParams({ + @ApiImplicitParam(name = "session_id", required = true, dataType = STRING, paramType = QUERY, value = "OTP SessionId") + + }) + @ApiResponses(value = {@ApiResponse(code = 200, message = "In response their will be otp session id with expiry duration" + + "in seconds \n" + + "{\n" + + " \"data\": {\n" + + " \"otp_session_id\": \"\",\n" + + " \"expiry_duration\": \n" + + " },\n" + + " \"status\": 200\n" + + "}" + )})*/ + @PostMapping(value = "/v1/resendOtp", produces = "application/json") + @Timed(value = "resendOtp.request", histogram = true, extraTags = {"version", "0.1"}) + public Map resendOtp(@RequestParam("session_id") UUID sessionId) // OTP SESSION-ID + { + + /*System.out.println(""); + System.out.println("RESEND OTP CONTROLLER"); + System.out.println(""); + System.out.println("otpSession : " + sessionId); + System.out.println("");*/ + + logger.info("Resend OTP API"); + logger.info("Parameters: "); + logger.info("session_id: " + sessionId); + + OtpSessions otpSessions = otpSessionsService.getOtpSessions(sessionId); + + /*System.out.println(""); + System.out.println("OTP Sessions"); + System.out.println(otpSessions); + System.out.println("");*/ + + if (otpSessions == null) { + + /* System.out.println(""); + System.out.println("OTP Sessions doesn't exists"); + System.out.println("");*/ + + logger.warn("OTP sessions doesn't exists"); + + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, null, OTP_SESSION_NOT_EXISTS); + } + + + if (otpSessions.getIs_verified() == Constants.VerificationStatus.VERIFIED) { + + /*System.out.println(""); + System.out.println("OTP is already Verified"); + System.out.println("");*/ + + logger.info("OTP is already Verified"); + + Map responseData = new HashMap(1); + responseData.put("is_verified", Constants.VerificationStatus.VERIFIED); + + return HttpResponse.getResponse(SUCCESS_STATUS_CODE, responseData, OTP_IS_ALREADY_VERIFIED); + + } + + if (airdropProfilesService.getAirdropProfiles(otpSessions.getAccount_id(), null).get(0).getIs_verified() == Constants.VerificationStatus.VERIFIED) { + + /*System.out.println(""); + System.out.println("Airdrop Profile is already Verified"); + System.out.println("");*/ + + logger.info("Airdrop Profile is already Verified"); + + Map responseData = new HashMap(1); + responseData.put("is_verified", Constants.VerificationStatus.VERIFIED); + + return HttpResponse.getResponse(SUCCESS_STATUS_CODE, responseData, OTP_IS_ALREADY_VERIFIED); + + } + + + Map responseData = new HashMap(1); + + // If OTP session is expired + // if (otpSessions.getExpired_ts() < AirdropUtil.generateCurrentEpochTimeStamp()) { + + + long epochCreationTimeStamp = AirdropUtil.generateCurrentEpochTimeStamp(); + + UUID newOtpSessionId = UUID.randomUUID(); + int otp = AirdropUtil.generateOtpNumber(); + + OtpSessions otpSessionsObj = new OtpSessions(); + otpSessionsObj.setAccount_id(otpSessions.getAccount_id()); + otpSessionsObj.setId(newOtpSessionId); + otpSessionsObj.setCreated_ts(epochCreationTimeStamp); + otpSessionsObj.setOtp(otp); + otpSessionsObj.setIs_verified(Constants.VerificationStatus.NOT_VERIFIED); + otpSessionsObj.setVerified_ts(0L); + otpSessionsObj.setExpired_ts((epochCreationTimeStamp + OTP_EXPIRE_TIME)); + + + /*System.out.println(""); + System.out.println("New Otp Session Created"); + System.out.println("");*/ + + logger.info("New Otp Session Created"); + + responseData.put("otp_session_id", newOtpSessionId); + responseData.put("expiry_duration", OTP_EXPIRE_TIME); + /*} else { + + System.out.println(""); + System.out.println("This OTP session is not expired yet"); + System.out.println(""); + + responseData.put("otp_session_id", otpSessions.getId()); + }*/ + + otpSessionsService.addOtpSessions(otpSessionsObj); + + /* + + // HERE, NYNJA PUSH SERVICE WILL BE CALLED TO SEND NOTIFICATION TO USER + + */ + restTemplateUtil.sendPushNotification(otpSessions.getAccount_id().toString(), otp); + + return HttpResponse.getResponse(SUCCESS_STATUS_CODE, responseData, null); + } + + + /** + * Function fetches all action details of user + * + * @param "account_id" type="UUID" otp session id + * @param "action_type" (not required) type="String" user action type + * @return "status" : 200/400, "data" : {"account_id": , "action_type" : , "count" : ""} + * @link http:///airdrop/v1/eventData + * @version 0.1 + * @produces application/json + */ + + @GetMapping(value = "/v1/eventData", produces = "application/json") + /* @ApiOperation(value = "This API will fetch user Action Details (Events triggered by User), Example - JOIN") + @ApiImplicitParams({ + @ApiImplicitParam(name = "account_id", required = true, dataType = STRING, paramType = QUERY, value = "User airdrop Profile Account Id"), + @ApiImplicitParam(name = "action_type", required = false, dataType = STRING, paramType = QUERY, value = "Action Example - JOIN") + }) + @ApiResponses(value = {@ApiResponse(code = 200, message = "In response their will be log of Actions taken by user \n" + + "{\n" + + " \"data\": [\n" + + " {\n" + + " \"account_id\": \"\",\n" + + " \"action_type\": \"\",\n" + + " \"count\": \n" + + " },\n" + + " . . . . . . . .\n" + + " ],\n" + + " \"status\": 200\n" + + "}" + )})*/ + @Timed(value = "eventData.request", histogram = true, extraTags = {"version", "0.1"}) + public Map getEventsData(@RequestParam("account_id") UUID accountId, + @RequestParam(value = "action_type", required = false) String actionType) { + + + /*System.out.println(""); + System.out.println("GET EVENTS DATA CONTROLLER"); + System.out.println(""); + System.out.println("accountId : " + accountId); + System.out.println("actionType : " + actionType); + System.out.println("");*/ + + + logger.info("Events Data Controller"); + logger.info("account_id: " + accountId); + logger.info("action_type: " + actionType); + + + if (actionType != null && !actionType.equals(Constants.KafkaActionType.JOIN)) { + + /*System.out.println(""); + System.out.println("Invalid actionType"); + System.out.println("");*/ + + logger.warn("Invalid actionType"); + + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, null, INVALID_KAFKA_ACTION_TYPE); + + } + + List airdropProfilesList = airdropProfilesService.getAirdropProfiles(accountId, null); + +/* System.out.println(""); + System.out.println("Airdrop-Profile-List"); + System.out.println(airdropProfilesList); + System.out.println("");*/ + + if (airdropProfilesList.isEmpty()) { + + /*System.out.println(""); + System.out.println("No, such Airdrop Profile exists"); + System.out.println("");*/ + + logger.warn("No Airdrop Profile exists"); + + return HttpResponse.getResponse(BAD_REQUEST_STATUS_CODE, null, HttpConstants.MessageTags.AIRDROP_PROFILE_NOT_EXISTS); + } + + + List airdropActionsList = airdropActionsService.getAirDropActions(accountId, actionType); + + /*System.out.println(""); + System.out.println("AirdropActionsList"); + System.out.println(airdropActionsList); + System.out.println("");*/ + + return (airdropActionsList.isEmpty()) ? HttpResponse.getResponse(CONTENT_NOT_FOUND_STATUS_CODE, null, NO_ACTION_EVENT_FOUND) : + HttpResponse.getResponse(SUCCESS_STATUS_CODE, airdropActionsList, null); + + } + + + /** + * Function fetches all failed action details + * + * @return "status" : 200/400, "data" : {"account_id": , "action_type" : , "count" : ""} + * @link http:///airdrop/v1/failedEventData + * @version 0.1 + * @produces application/json + */ + + @GetMapping(value = "/v1/failedEventData", produces = "application/json") + /* @ApiOperation(value = "This API will fetch all failed Action Details (Events triggered by User), Example - JOIN") + @ApiResponses(value = {@ApiResponse(code = 200, message = "In response their will be log of all Actions \n" + + "{\n" + + " \"data\": [\n" + + " {\n" + + " \"account_id\": \"\",\n" + + " \"action_type\": \"\",\n" + + " \"count\": \n" + + " },\n" + + " . . . . . . . .\n" + + " ],\n" + + " \"status\": 200\n" + + "}" + )})*/ + @Timed(value = "failedEventData.request", histogram = true, extraTags = {"version", "0.1"}) + public Map getFailedEventsData() { + + + /*System.out.println(""); + System.out.println("GET EVENTS DATA CONTROLLER"); + System.out.println(""); + System.out.println("accountId : " + accountId); + System.out.println("actionType : " + actionType); + System.out.println("");*/ + + + logger.info("Failed Events Data Controller"); + + List failedActions = actionsFailedService.getAllActionsFailed(); + + return (failedActions.isEmpty()) ? HttpResponse.getResponse(CONTENT_NOT_FOUND_STATUS_CODE, null, NO_ACTION_EVENT_FOUND) : + HttpResponse.getResponse(SUCCESS_STATUS_CODE, failedActions, null); + + } + + + /** + * Function fetches all failed action details(actions which were unsuccessfull to notify qoin-pro). + * than it again calls qoinPro service to notify it again with actions(failed actions) + * After successfully notifying QoinPro. The actions will be removed from ActionsFailed Table + * + * @return { + * "message": "Actions are successfully notified to QoinPro.", + * "message_code": 15, + * "status": 200 + * } + * @link http:///airdrop/v1/failedEventData + * @version 0.1 + * @produces application/json + */ + + @PostMapping(value = "/v1/notifyFailedEvents", produces = "application/json") + /* @ApiOperation(value = "This API will re-notify failed actions('Actions which were failed to notify qoinPro') to qoinPro") + @ApiResponses(value = {@ApiResponse(code = 200, message = "In response their will be a notification acknowledgement" + + "{\n" + + " \"message\": \"Actions are successfully notified to QoinPro.\",\n" + + " \"message_code\": 15,\n" + + " \"status\": 200\n" + + "}" + )})*/ + @Timed(value = "notifyFailedEventData.request", histogram = true, extraTags = {"version", "0.1"}) + public Map notifyFailedEvents() { + + logger.info("Notify Failed Events Controller"); + + List actionsFailedList = actionsFailedService.getAllActionsFailed(); + + if (actionsFailedList == null || actionsFailedList.size() == 0) { + logger.info("No, failed events exists"); + + HttpResponse.getResponse(CONTENT_NOT_FOUND_STATUS_CODE, null, NO_ACTION_TO_NOTIFY); + } + + + Set actionFailedIdsToRemove = new HashSet<>(actionsFailedList.size()); + + for (ActionsFailed actionsFailed : actionsFailedList) { + String response = restTemplateUtil.qoinProChannelEngagementAPI(actionsFailed.getAccount_id().toString(), actionsFailed.getUsername() + , actionsFailed.getAction_type(), actionsFailed.getAction_detail()); + + if (response.equals("Success")) { + actionFailedIdsToRemove.add(actionsFailed.getId()); + List airdropActions = airdropActionsService.getAirDropActions(actionsFailed.getAccount_id(), actionsFailed.getAction_type()); + + if (airdropActions == null || airdropActions.isEmpty()) { + + logger.info("Adding action in airdrop_actions table for username: {} and action_type: {}", actionsFailed.getUsername(), actionsFailed.getAction_type()); + + AirdropActions airdropActionsObj = new AirdropActions(); + airdropActionsObj.setId(UUID.randomUUID()); + airdropActionsObj.setAccount_id(actionsFailed.getAccount_id()); + airdropActionsObj.setAction_type(actionsFailed.getAction_type()); + airdropActionsObj.setCount(1); + airdropActionsObj.setLast_action_ts(AirdropUtil.generateCurrentEpochTimeStamp()); + + airdropActionsService.addAirdropActions(airdropActionsObj); + + logger.info("Airdrop Action is Added"); + + } else { + + if (!actionsFailed.getAction_type().equals(Constants.KafkaActionType.JOIN)) { + + logger.info("Updating action count in airdrop_actions table for username: {} and action_type: {}", actionsFailed.getUsername(), actionsFailed.getAction_type()); + Map updateMap = new HashMap(); + updateMap.put("count", airdropActions.get(0).getCount() + 1); + updateMap.put("last_action_ts", AirdropUtil.generateCurrentEpochTimeStamp()); + + airdropActionsService.updateAirdropActions(airdropActions.get(0).getAccount_id(), airdropActions.get(0).getId(), airdropActions.get(0).getAction_type(), updateMap); + logger.info("Airdrop Action is Updated"); + + } else { + logger.info(Constants.KafkaActionType.JOIN + " action is already performed it's count won't increase"); + } + } + } + } + + if (actionFailedIdsToRemove.isEmpty()) { + logger.info("Failed to re-notify qoin-pro. No data will be removed from actions_failed table"); + return HttpResponse.getResponse(SERVICE_UNAVAILABLE_STATUS_CODE, null, UNABLE_TO_NOTIFY_QOIN_PRO); + + } else { + logger.info("Failed Actions which are successfully notified to qoin-pro again : {}", actionFailedIdsToRemove); + } + + + if (actionFailedIdsToRemove.size() != 0) { + logger.info("Deleting actions from actions_failed table"); + boolean deleteResponse = actionsFailedService.deleteActionsFailed(actionFailedIdsToRemove); + logger.info("DeleteResponse: {}", deleteResponse); + } + + logger.info("Successfully removed actions from actionFailed Table."); + + + return HttpResponse.getResponse(SUCCESS_STATUS_CODE, null, FAILED_ACTIONS_NOTIFIED_SUCCESSFULLY); + + } + + +} diff --git a/src/main/java/biz/nynja/airdrop/entity/ActionsFailed.java b/src/main/java/biz/nynja/airdrop/entity/ActionsFailed.java new file mode 100644 index 0000000000000000000000000000000000000000..cfe7d05d165e11862f5e08ce1eb4ec15aee83af9 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/entity/ActionsFailed.java @@ -0,0 +1,88 @@ +package biz.nynja.airdrop.entity; + +import org.springframework.data.cassandra.core.cql.PrimaryKeyType; +import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn; +import org.springframework.data.cassandra.core.mapping.Table; +import java.util.UUID; + +@Table(value = "actions_failed") +public class ActionsFailed { + + @PrimaryKeyColumn(ordinal = 0, name = "id", type = PrimaryKeyType.PARTITIONED) + private UUID id; + private UUID account_id; + private String username; + private String action_type; + private String action_detail; + private Long created_ts; + private String channel; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getAccount_id() { + return account_id; + } + + public void setAccount_id(UUID account_id) { + this.account_id = account_id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getAction_type() { + return action_type; + } + + public void setAction_type(String action_type) { + this.action_type = action_type; + } + + public String getAction_detail() { + return action_detail; + } + + public void setAction_detail(String action_detail) { + this.action_detail = action_detail; + } + + public Long getCreated_ts() { + return created_ts; + } + + public void setCreated_ts(Long created_ts) { + this.created_ts = created_ts; + } + + public String getChannel() { + return channel; + } + + public void setChannel(String channel) { + this.channel = channel; + } + + @Override + public String toString() { + return "ActionsFailed{" + + "id=" + id + + ", account_id=" + account_id + + ", username='" + username + '\'' + + ", action_type='" + action_type + '\'' + + ", action_detail='" + action_detail + '\'' + + ", created_ts=" + created_ts + + ", channel='" + channel + '\'' + + '}'; + } +} diff --git a/src/main/java/biz/nynja/airdrop/entity/ActionsSummary.java b/src/main/java/biz/nynja/airdrop/entity/ActionsSummary.java new file mode 100644 index 0000000000000000000000000000000000000000..58ca59841b7d1e6670a7e5a741f8044c763a403d --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/entity/ActionsSummary.java @@ -0,0 +1,59 @@ +package biz.nynja.airdrop.entity; + +import org.springframework.data.cassandra.core.cql.PrimaryKeyType; +import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn; +import org.springframework.data.cassandra.core.mapping.Table; +import java.util.UUID; + +@Table(value = "actions_summary") +public class ActionsSummary { + + @PrimaryKeyColumn(ordinal = 0, name = "action", type = PrimaryKeyType.PARTITIONED) + private String action; + @PrimaryKeyColumn(ordinal = 1, name = "id", type = PrimaryKeyType.CLUSTERED) + private UUID id; + private Short is_active; + private String action_description; + + public String getAction_description() { + return action_description; + } + + public void setAction_description(String action_description) { + this.action_description = action_description; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public Short getIs_active() { + return is_active; + } + + public void setIs_active(Short is_active) { + this.is_active = is_active; + } + + @Override + public String toString() { + return "ActionsSummary{" + + "action='" + action + '\'' + + ", id=" + id + + ", is_active=" + is_active + + ", action_description=" + action_description + + '}'; + } +} diff --git a/src/main/java/biz/nynja/airdrop/entity/AirdropActions.java b/src/main/java/biz/nynja/airdrop/entity/AirdropActions.java new file mode 100644 index 0000000000000000000000000000000000000000..bba1666d7a08c145fca73d785efa08ba4d44a495 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/entity/AirdropActions.java @@ -0,0 +1,75 @@ +package biz.nynja.airdrop.entity; + + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.springframework.data.cassandra.core.cql.PrimaryKeyType; +import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn; +import org.springframework.data.cassandra.core.mapping.Table; +import java.util.UUID; + +@Table(value = "airdrop_actions") +public class AirdropActions { + + @PrimaryKeyColumn(name = "account_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED) + private UUID account_id; + @PrimaryKeyColumn(name = "id", ordinal = 1, type = PrimaryKeyType.CLUSTERED) + @JsonIgnore + private UUID id; + @PrimaryKeyColumn(name = "action_type", ordinal = 2, type = PrimaryKeyType.CLUSTERED) + private String action_type; // "contact", "sms", "videocall" + @JsonIgnore + private Long last_action_ts; + private Integer count; + + + public UUID getAccount_id() { + return account_id; + } + + public void setAccount_id(UUID account_id) { + this.account_id = account_id; + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getAction_type() { + return action_type; + } + + public void setAction_type(String action_type) { + this.action_type = action_type; + } + + public Long getLast_action_ts() { + return last_action_ts; + } + + public void setLast_action_ts(Long last_action_ts) { + this.last_action_ts = last_action_ts; + } + + public Integer getCount() { + return count; + } + + public void setCount(Integer count) { + this.count = count; + } + + @Override + public String toString() { + return "AirdropActions{" + + "account_id=" + account_id + + ", id=" + id + + ", action_type='" + action_type + '\'' + + ", last_action_ts=" + last_action_ts + + ", count=" + count + + '}'; + } +} diff --git a/src/main/java/biz/nynja/airdrop/entity/AirdropProfiles.java b/src/main/java/biz/nynja/airdrop/entity/AirdropProfiles.java new file mode 100644 index 0000000000000000000000000000000000000000..870cc94f8a74ef4b1c5e311249268a55f42a45c2 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/entity/AirdropProfiles.java @@ -0,0 +1,101 @@ +package biz.nynja.airdrop.entity; + +import org.springframework.data.cassandra.core.cql.PrimaryKeyType; +import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn; +import org.springframework.data.cassandra.core.mapping.Table; + +import java.util.UUID; + +@Table("airdrop_profiles") +public class AirdropProfiles { + + @PrimaryKeyColumn(ordinal = 0, name = "account_id", type = PrimaryKeyType.PARTITIONED) + private UUID account_id; + @PrimaryKeyColumn(ordinal = 1, name = "id", type = PrimaryKeyType.CLUSTERED) + private UUID id; + private String username; // Index + private String referrer_code; + private Long created_ts; + private Long modified_ts; + private Short is_verified; + private Long verified_ts; + + public String getReferrer_code() { + return referrer_code; + } + + public void setReferrer_code(String referrer_code) { + this.referrer_code = referrer_code; + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getAccount_id() { + return account_id; + } + + public void setAccount_id(UUID account_id) { + this.account_id = account_id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Long getCreated_ts() { + return created_ts; + } + + public void setCreated_ts(Long created_ts) { + this.created_ts = created_ts; + } + + public Long getModified_ts() { + return modified_ts; + } + + public void setModified_ts(Long modified_ts) { + this.modified_ts = modified_ts; + } + + public Short getIs_verified() { + return is_verified; + } + + public void setIs_verified(Short is_verified) { + this.is_verified = is_verified; + } + + public Long getVerified_ts() { + return verified_ts; + } + + public void setVerified_ts(Long verified_ts) { + this.verified_ts = verified_ts; + } + + + @Override + public String toString() { + return "AirdropProfiles{" + + "id=" + id + + ", account_id=" + account_id + + ", username='" + username + '\'' + + ", referrer_code='" + referrer_code + '\'' + + ", created_ts=" + created_ts + + ", modified_ts=" + modified_ts + + ", is_verified=" + is_verified + + ", verified_ts=" + verified_ts + + '}'; + } +} diff --git a/src/main/java/biz/nynja/airdrop/entity/OtpSessions.java b/src/main/java/biz/nynja/airdrop/entity/OtpSessions.java new file mode 100644 index 0000000000000000000000000000000000000000..69b79b673e847fc064b29b1e256616e369203839 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/entity/OtpSessions.java @@ -0,0 +1,98 @@ +package biz.nynja.airdrop.entity; + +import org.springframework.data.cassandra.core.cql.PrimaryKeyType; +import org.springframework.data.cassandra.core.mapping.PrimaryKeyColumn; +import org.springframework.data.cassandra.core.mapping.Table; + +import java.util.UUID; + +@Table(value = "otp_sessions") +public class OtpSessions { + + @PrimaryKeyColumn(ordinal = 0, name = "id", type = PrimaryKeyType.PARTITIONED) + private UUID id; + private UUID account_id; + private Long created_ts; + private Integer otp; + private Short is_verified; + private Long verified_ts; + private Long expired_ts; + public static Long test; + + public Long getTest() { + return test; + } + + public void setTest(Long test) { + OtpSessions.test = test; + } + + public Long getExpired_ts() { + return expired_ts; + } + + public void setExpired_ts(Long expired_ts) { + this.expired_ts = expired_ts; + } + + public UUID getAccount_id() { + return account_id; + } + + public void setAccount_id(UUID account_id) { + this.account_id = account_id; + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public Long getCreated_ts() { + return created_ts; + } + + public void setCreated_ts(Long created_ts) { + this.created_ts = created_ts; + } + + public Integer getOtp() { + return otp; + } + + public void setOtp(Integer otp) { + this.otp = otp; + } + + public Short getIs_verified() { + return is_verified; + } + + public void setIs_verified(Short is_verified) { + this.is_verified = is_verified; + } + + public Long getVerified_ts() { + return verified_ts; + } + + public void setVerified_ts(Long verified_ts) { + this.verified_ts = verified_ts; + } + + @Override + public String toString() { + return "OtpSessions{" + + "account_id=" + account_id + + ", id=" + id + + ", created_ts=" + created_ts + + ", otp=" + otp + + ", is_verified=" + is_verified + + ", verified_ts=" + verified_ts + + ", expired_ts=" + expired_ts + + '}'; + } +} diff --git a/src/main/java/biz/nynja/airdrop/grpc/AccountServiceClient.java b/src/main/java/biz/nynja/airdrop/grpc/AccountServiceClient.java new file mode 100644 index 0000000000000000000000000000000000000000..27c63c272c7e0275959800e85fae098e0aa2e2de --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/grpc/AccountServiceClient.java @@ -0,0 +1,105 @@ +package biz.nynja.airdrop.grpc; + +import biz.nynja.account.grpc.*; +import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.stub.MetadataUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import javax.annotation.PostConstruct; +import java.util.UUID; + +@Component +public class AccountServiceClient { + + @Autowired + @Qualifier("accountServiceManagedChannel") + private ManagedChannel managedChannel; + private AccountServiceGrpc.AccountServiceBlockingStub accountServiceStub; + + +/* + public UUID verifyAccount(String username) { + + verifyAccountRequest request = verifyAccountRequest.newBuilder().setUsername(username).build(); + + System.out.println(""); + System.out.println("Requesting Account : "); + System.out.println(username); + System.out.println(""); + + verifyAccountResponse response = verifyAccountServiceBlockingStub.verifyAccount(request); + + System.out.println(""); + System.out.println("Response : "); + System.out.println(response); + System.out.println(""); + + return (response.getAccountId().equals("none")) ? null : UUID.fromString(response.getAccountId()); + + } +*/ + + + public UUID verifyAccount(String username, String token) { + + System.out.println(""); + System.out.println("-----------------------------"); + System.out.println("Verify Username GRPC function"); + System.out.println(""); + System.out.println("username : " + username); + System.out.println("token :" + token); + System.out.println(""); + + GetByUsernameRequest request = GetByUsernameRequest.newBuilder().setUsername(username).build(); + + Metadata header = new Metadata(); + Metadata.Key key = + Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER); + header.put(key, "Bearer " + token); + + + accountServiceStub = MetadataUtils.attachHeaders(accountServiceStub, header); + + SearchResponse searchByUserNameResponse = accountServiceStub.searchByUsername(request); + ErrorResponse errorResponse = searchByUserNameResponse.getError(); + + SearchResultDetails searchResultDetails = searchByUserNameResponse.getSearchResultDetails(); + System.out.println("-----------------------------"); + System.out.println(""); + System.out.println("SearchByUserNameResponse : "); + System.out.println(searchByUserNameResponse); + System.out.println(""); + System.out.println("-----------------------------"); + System.out.println(""); + System.out.println("ErrorResponse : "); + System.out.println(errorResponse); + System.out.println(""); + System.out.println("-----------------------------"); + System.out.println(""); + System.out.println("SearchResultDetails : "); + System.out.println(searchResultDetails); + System.out.println(""); + + + if (searchResultDetails == null) { + + System.out.println(""); + System.out.println("SearchResultDetails is Null"); + System.out.println(""); + + return null; + } + + return UUID.fromString(searchResultDetails.getAccountId()); + + } + + + @PostConstruct + public void init() { + accountServiceStub = AccountServiceGrpc.newBlockingStub(managedChannel); + } + +} diff --git a/src/main/java/biz/nynja/airdrop/grpc/AccountServiceClientOld.java b/src/main/java/biz/nynja/airdrop/grpc/AccountServiceClientOld.java new file mode 100644 index 0000000000000000000000000000000000000000..f2069ccf700fb47270f294907fc26c9f613f3b2b --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/grpc/AccountServiceClientOld.java @@ -0,0 +1,52 @@ +/*package biz.nynja.airdrop.grpc; + +import io.grpc.ManagedChannel; +import org.grpc.service.verifyAccountRequest; +import org.grpc.service.verifyAccountResponse; +import org.grpc.service.verifyAccountServiceGrpc; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.UUID;*/ + + +/* +@Component +public class AccountServiceClientOld { + + @Autowired + private ManagedChannel managedChannel; + private verifyAccountServiceGrpc.verifyAccountServiceBlockingStub verifyAccountServiceBlockingStub; + + + public UUID verifyAccount(String username) { + + verifyAccountRequest request = verifyAccountRequest.newBuilder().setUsername(username).build(); + + System.out.println(""); + System.out.println("Requesting Account : "); + System.out.println(username); + System.out.println(""); + + verifyAccountResponse response = verifyAccountServiceBlockingStub.verifyAccount(request); + + System.out.println(""); + System.out.println("Response : "); + System.out.println(response); + System.out.println(""); + + return (response.getAccountId().equals("none")) ? null : UUID.fromString(response.getAccountId()); + + } + + @PostConstruct + public void init() { + + verifyAccountServiceBlockingStub = verifyAccountServiceGrpc.newBlockingStub(managedChannel); + + } + + +} +*/ diff --git a/src/main/java/biz/nynja/airdrop/grpc/AuthServiceClient.java b/src/main/java/biz/nynja/airdrop/grpc/AuthServiceClient.java new file mode 100644 index 0000000000000000000000000000000000000000..bdd8113d3d655c6ac1886addd827f9b12f9063ce --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/grpc/AuthServiceClient.java @@ -0,0 +1,183 @@ +package biz.nynja.airdrop.grpc; + +import biz.nynja.authentication.grpc.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.grpc.ManagedChannel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + +@Component +public class AuthServiceClient extends AuthenticationServiceGrpc.AuthenticationServiceImplBase { + + + @Autowired + @Qualifier("authServiceManagedChannel") + private ManagedChannel managedChannel; + private AuthenticationServiceGrpc.AuthenticationServiceBlockingStub authenticationServiceStub; + + private ObjectMapper objectMapper; + public Map tokens; + + @PostConstruct + public void init() throws URISyntaxException { + + authenticationServiceStub = AuthenticationServiceGrpc.newBlockingStub(managedChannel); + objectMapper = new ObjectMapper(); + + try { + + System.out.println(""); + System.out.println("Reading Access and Refresh Tokens"); + System.out.println(""); + + tokens = objectMapper.readValue(AuthServiceClient.class.getClassLoader().getResourceAsStream("token/token.json"), Map.class); + + System.out.println("Tokens : "); + System.out.println(tokens); + System.out.println(""); + + + } catch (IOException e) { + e.printStackTrace(); + } + + } + + + + public boolean exchangeRefreshTokenManual(String refreshToken) throws IOException { + + System.out.println(""); + System.out.println("---------------"); + System.out.println(""); + System.out.println("Generating ExchangeRefreshToken Request"); + System.out.println(""); + System.out.println("Refresh-Token : " + refreshToken); + System.out.println(""); + System.out.println("---------------"); + System.out.println(""); + + ExchangeRefreshTokenRequest request = ExchangeRefreshTokenRequest.newBuilder().setRefreshToken(refreshToken).build(); + + GenerateTokenResponse generateTokenResponse = authenticationServiceStub.exchangeRefreshToken(request); + TokenResponseDetails tokenResponseDetails = generateTokenResponse.getTokenResponseDetails(); + ErrorResponse errorResponse = generateTokenResponse.getError(); + + System.out.println(""); + System.out.println("TokenResponseDetails: "); + System.out.println(tokenResponseDetails); + System.out.println(""); + System.out.println("--------------------"); + System.out.println(""); + System.out.println("ErrorResponse: "); + System.out.println(errorResponse); + System.out.println(""); + + if (tokenResponseDetails == null) { + + System.out.println(""); + System.out.println("Exchange Refresh Token Failed"); + System.out.println(""); + + return false; + } + + String accessToken = tokenResponseDetails.getToken(); + String newRefreshToken = tokenResponseDetails.getRefreshToken(); + + System.out.println(""); + System.out.println("New Access Token : " + accessToken); + System.out.println("New Refresh Token : " + newRefreshToken); + System.out.println(""); + + return true; + } + + public boolean exchangeRefreshToken() throws IOException { + + System.out.println(""); + System.out.println("---------------"); + System.out.println(""); + System.out.println("Generating ExchangeRefreshToken Request"); + System.out.println(""); + System.out.println("Refresh-Token : " + tokens.get("refreshToken")); + System.out.println(""); + System.out.println("---------------"); + System.out.println(""); + + ExchangeRefreshTokenRequest request = ExchangeRefreshTokenRequest.newBuilder().setRefreshToken(tokens.get("refreshToken")).build(); + + GenerateTokenResponse generateTokenResponse = authenticationServiceStub.exchangeRefreshToken(request); + TokenResponseDetails tokenResponseDetails = generateTokenResponse.getTokenResponseDetails(); + ErrorResponse errorResponse = generateTokenResponse.getError(); + + System.out.println(""); + System.out.println("TokenResponseDetails: "); + System.out.println(tokenResponseDetails); + System.out.println(""); + System.out.println("--------------------"); + System.out.println(""); + System.out.println("ErrorResponse: "); + System.out.println(errorResponse); + System.out.println(""); + + if (tokenResponseDetails == null) { + + System.out.println(""); + System.out.println("Exchange Refresh Token Failed"); + System.out.println(""); + + return false; + } + + String accessToken = tokenResponseDetails.getToken(); + String refreshToken = tokenResponseDetails.getRefreshToken(); + + System.out.println(""); + System.out.println("New Access Token : " + accessToken); + System.out.println("New Refresh Token : " + refreshToken); + System.out.println(""); + + + if (accessToken.equals(tokens.get("accessToken")) || refreshToken.equals(tokens.get("refreshToken"))) { + + System.out.println(""); + System.out.println("Old and New accessTokens are same"); + System.out.println("Or old and new refreshToken are same"); + System.out.println(""); + System.out.println("So, file token won't be updated"); + System.out.println(""); + + } else { + + System.out.println(""); + System.out.println("Updating Tokens File"); + System.out.println(""); + + // Token File Updation Code + File outputDirectory = new File(AuthServiceClient.class.getProtectionDomain().getCodeSource().getLocation().getFile() + "/token/token.json"); + + Map newTokenDetails = new HashMap<>(2); + newTokenDetails.put("accessToken", accessToken); + newTokenDetails.put("refreshToken", refreshToken); + + objectMapper.writeValue(outputDirectory, newTokenDetails); + + System.out.println(""); + System.out.println("Token File is updated"); + System.out.println(""); + + } + + return true; + } + +} diff --git a/src/main/java/biz/nynja/airdrop/interceptor/KeyInterceptor.java b/src/main/java/biz/nynja/airdrop/interceptor/KeyInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..7745cd4fb3534969bca395545d98078640175278 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/interceptor/KeyInterceptor.java @@ -0,0 +1,51 @@ +/* +package biz.nynja.airdrop.interceptor; + +import biz.nynja.airdrop.controller.AirdropController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +*/ + + +/*@Component +public class KeyInterceptor implements HandlerInterceptor { + + private static final Logger logger = LoggerFactory.getLogger(KeyInterceptor.class); + + + @Value("${authentication.airdrop.header.name}") + private String airdropAuthenticationHeaderName; + + @Value("${authentication.airdrop.header.value}") + private String airdropAuthenticationHeaderValue; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { + + + String airdropHeader = request.getHeader(airdropAuthenticationHeaderName); + + logger.info("Header Validation"); + logger.info(airdropAuthenticationHeaderName + " : " + airdropHeader); + + if (airdropHeader == null) { + logger.warn("header key is missing"); + logger.warn("key is required: " + airdropAuthenticationHeaderName); + return false; + } else if (!airdropHeader.equals(airdropAuthenticationHeaderValue)) { + logger.warn("key value doesn't match"); + logger.warn("key is required: " + airdropAuthenticationHeaderName); + return false; + } + + logger.info("header key matched"); + return true; + } +}*/ diff --git a/src/main/java/biz/nynja/airdrop/kafka/KafkaConsumer.java b/src/main/java/biz/nynja/airdrop/kafka/KafkaConsumer.java new file mode 100644 index 0000000000000000000000000000000000000000..5e64f6267d4014201e6bb8d4642b66477c23b256 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/kafka/KafkaConsumer.java @@ -0,0 +1,179 @@ +package biz.nynja.airdrop.kafka; + +import biz.nynja.airdrop.constants.Constants; +import biz.nynja.airdrop.controller.AirdropController; +import biz.nynja.airdrop.entity.ActionsFailed; +import biz.nynja.airdrop.entity.ActionsSummary; +import biz.nynja.airdrop.entity.AirdropActions; +import biz.nynja.airdrop.entity.AirdropProfiles; +import biz.nynja.airdrop.service.ActionSummaryService; +import biz.nynja.airdrop.service.ActionsFailedService; +import biz.nynja.airdrop.service.AirdropActionsService; +import biz.nynja.airdrop.service.AirdropProfilesService; +import biz.nynja.airdrop.util.AirdropUtil; +import biz.nynja.airdrop.util.RestTemplateUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.Acknowledgment; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Component +public class KafkaConsumer { + + @Autowired + private AirdropActionsService airdropActionsService; + + @Autowired + private ActionSummaryService actionSummaryService; + + @Autowired + private AirdropProfilesService airdropProfilesService; + + @Autowired + private ActionsFailedService actionsFailedService; + + @Autowired + private RestTemplateUtil restTemplateUtil; + + private static final Logger logger = LoggerFactory.getLogger(KafkaConsumer.class); + + /*@KafkaListener(groupId = "#{@kafkaGroup}", topicPartitions = + {@TopicPartition(topic = "#{@kafkaTopic}", partitionOffsets = @PartitionOffset(partition = "0", initialOffset = "0"))} + , containerFactory = "kafkaListenerFactory")*/ + @KafkaListener(topics = "#{@kafkaTopic}", groupId = "#{@kafkaGroup}", containerFactory = "kafkaListenerFactory") + public void kafkaConsumer(KafkaMessageTemplate kafkaMessageTemplate, Acknowledgment acknowledgment) { + + + /*System.out.println("KAFKA-MESSAGE"); + System.out.println(kafkaMessageTemplate);*/ + + logger.info("New Kafka Messsage Consumed: "); + logger.info(kafkaMessageTemplate.toString()); + + UUID accountId = UUID.fromString(kafkaMessageTemplate.getAccount_id()); + + List airdropProfilesList = airdropProfilesService.getAirdropProfiles(accountId, null); + /*System.out.println(""); + System.out.println("airdropProfilesList : "); + System.out.println(airdropProfilesList);*/ + + ActionsSummary actionsSummary = actionSummaryService.getActionsSummary(kafkaMessageTemplate.getAction_type()); + /*System.out.println(""); + System.out.println("actionsSummary : "); + System.out.println(actionsSummary);*/ + + if (airdropProfilesList.isEmpty() || actionsSummary == null) { + + /*System.out.println(""); + System.out.println("No Action Summary Exists"); + System.out.println("No, Action will be performed on this Kafka Message");*/ + + logger.warn("No Action Summary Exists for consumed kafkaMessage"); + logger.warn("No, Action will be performed on this Kafka Message"); + + } else if (actionsSummary.getIs_active() == Constants.KafkaActionTypeActivationStatus.UNACTIVE) { + + /*System.out.println(""); + System.out.println("ACTION TYPE IS NOT ACTIVE"); + System.out.println("No, Action will be performed on this Kafka Message");*/ + + logger.warn("Action type is not active"); + logger.warn("No, Action will be performed on this Kafka Message"); + + + } else { + + List airdropActionsList = airdropActionsService.getAirDropActions(accountId, kafkaMessageTemplate.getAction_type()); + + // This flag is to check. If qoinpro successfully got this Action + Boolean notifiedQoinPro = null; + + if (airdropActionsList != null && !airdropActionsList.isEmpty() && airdropActionsList.get(0).getAction_type().equals(Constants.KafkaActionType.JOIN)) { + logger.info("" + Constants.KafkaActionType.JOIN + " Action is already consumed. So, It won't be notified to QuionPro again"); + } else { + String response = restTemplateUtil.qoinProChannelEngagementAPI(airdropProfilesList.get(0).getAccount_id().toString(), airdropProfilesList.get(0).getUsername(), kafkaMessageTemplate.getAction_type(), actionsSummary.getAction_description()); + notifiedQoinPro = (response.equals("Success")) ? true : false; + } + + // If qoinPro response message is "Success" than data will be added in AirdropActions Table + if (notifiedQoinPro == true) { + + if (airdropActionsList == null || airdropActionsList.isEmpty()) { + + // Integer lastCount = airdropActionsService.getLastCount(accountId, kafkaMessageTemplate.getAction_type()); + + // System.out.println(""); + // System.out.println("lastCount :- " + lastCount); + + // Generate Airdrop Action + AirdropActions airdropActions = new AirdropActions(); + airdropActions.setId(UUID.randomUUID()); + airdropActions.setAccount_id(UUID.fromString(kafkaMessageTemplate.getAccount_id())); + airdropActions.setAction_type(kafkaMessageTemplate.getAction_type()); + airdropActions.setCount(1); + airdropActions.setLast_action_ts(AirdropUtil.generateCurrentEpochTimeStamp()); + + airdropActionsService.addAirdropActions(airdropActions); + + /*System.out.println(""); + System.out.println("Airdrop Action is Generated");*/ + + logger.info("Airdrop Action is Generated"); + + } else if (!kafkaMessageTemplate.getAction_type().equals(Constants.KafkaActionType.JOIN)) { + + Map updateMap = new HashMap(); + updateMap.put("count", airdropActionsList.get(0).getCount() + 1); + updateMap.put("last_action_ts", AirdropUtil.generateCurrentEpochTimeStamp()); + + airdropActionsService.updateAirdropActions(accountId, airdropActionsList.get(0).getId(), kafkaMessageTemplate.getAction_type(), updateMap); + } else { + + /* System.out.println(""); + System.out.println("SignUp Action is already consumed. So, It won't be consumed again"); + System.out.println("");*/ + + logger.info("" + Constants.KafkaActionType.JOIN + " Action is already consumed. So, It won't be consumed again"); + + } + } else if(notifiedQoinPro == false){ + + // Response of qoinPro message is not "Success" So, this notification would be added in ActionsFailed Table + + logger.info("QoinPro API response is \"Failed\""); + logger.info("Adding action details in ActionFailed Table"); + + ActionsFailed actionsFailed = new ActionsFailed(); + actionsFailed.setId(UUID.randomUUID()); + actionsFailed.setChannel(Constants.QoinProChannel.NYNJA); + actionsFailed.setCreated_ts(AirdropUtil.generateCurrentEpochTimeStamp()); + actionsFailed.setAccount_id(UUID.fromString(kafkaMessageTemplate.getAccount_id())); + actionsFailed.setAction_type(kafkaMessageTemplate.getAction_type()); + actionsFailed.setUsername(airdropProfilesList.get(0).getUsername()); + actionsFailed.setAction_detail(actionsSummary.getAction_description()); + + actionsFailedService.addActionsFailed(actionsFailed); + + }else{ + logger.info("QoinPro API is not called"); + } + + + // NOTE : This Action will be performed early before adding data in Airdrop Actions Table. If this fails than It will be added to actionsFailed Table + // restTemplateUtil.qoinProChannelEngagementAPI(airdropActionsList.get(0).getAccount_id().toString(), airdropProfilesList.get(0).getUsername(), kafkaMessageTemplate.getAction_type(), actionsSummary.getAction_description()); + + } + + + acknowledgment.acknowledge(); + } +} + + diff --git a/src/main/java/biz/nynja/airdrop/kafka/KafkaMessageTemplate.java b/src/main/java/biz/nynja/airdrop/kafka/KafkaMessageTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..6d45d1c66e4195a243c794da781d19e6b0bf5a17 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/kafka/KafkaMessageTemplate.java @@ -0,0 +1,31 @@ +package biz.nynja.airdrop.kafka; + +public class KafkaMessageTemplate { + + private String account_id; + private String action_type; + + public String getAccount_id() { + return account_id; + } + + public void setAccount_id(String account_id) { + this.account_id = account_id; + } + + public String getAction_type() { + return action_type; + } + + public void setAction_type(String action_type) { + this.action_type = action_type; + } + + @Override + public String toString() { + return "KafkaMessageTemplate{" + + "account_id='" + account_id + '\'' + + ", action_type='" + action_type + '\'' + + '}'; + } +} diff --git a/src/main/java/biz/nynja/airdrop/kafka/KafkaProducer.java b/src/main/java/biz/nynja/airdrop/kafka/KafkaProducer.java new file mode 100644 index 0000000000000000000000000000000000000000..98d17230e82ba8bb2f8ec373842311da143ae92b --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/kafka/KafkaProducer.java @@ -0,0 +1,23 @@ +package biz.nynja.airdrop.kafka; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Component; + +@Component +public class KafkaProducer { + + @Autowired + private KafkaTemplate kafkaTemplate; + + @Autowired + @Qualifier("kafkaTopic") + private String kafkaTopic; + + + public void sendKafkaMessage(KafkaMessageTemplate kafkaMessageTemplate) { + kafkaTemplate.send(kafkaTopic, kafkaMessageTemplate); + } + +} diff --git a/src/main/java/biz/nynja/airdrop/repository/ActionSummaryRepository.java b/src/main/java/biz/nynja/airdrop/repository/ActionSummaryRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..a505d3715a5ebc156961b80370dea1010acfd4a7 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/repository/ActionSummaryRepository.java @@ -0,0 +1,26 @@ +package biz.nynja.airdrop.repository; + +import biz.nynja.airdrop.entity.ActionsSummary; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.cassandra.core.CassandraTemplate; +import org.springframework.data.cassandra.core.query.Criteria; +import org.springframework.data.cassandra.core.query.Query; +import org.springframework.stereotype.Repository; + +@Repository +public class ActionSummaryRepository { + + @Autowired + private CassandraTemplate cassandraTemplate; + + private static final Class ENTITY_CLASS = ActionsSummary.class; + + public ActionsSummary getActionsSummary(String action){ + Query query = Query.query(Criteria.where("action").is(action)); + return (ActionsSummary) cassandraTemplate.selectOne(query, ENTITY_CLASS); + } + + + + +} diff --git a/src/main/java/biz/nynja/airdrop/repository/ActionsFailedRepository.java b/src/main/java/biz/nynja/airdrop/repository/ActionsFailedRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..d5e7a95dc4972fc78d26cab368183ee61eea9ac2 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/repository/ActionsFailedRepository.java @@ -0,0 +1,38 @@ +package biz.nynja.airdrop.repository; + +import biz.nynja.airdrop.entity.ActionsFailed; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.cassandra.core.CassandraTemplate; +import org.springframework.data.cassandra.core.query.Criteria; +import org.springframework.data.cassandra.core.query.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +@Repository +public class ActionsFailedRepository { + + @Autowired + private CassandraTemplate cassandraTemplate; + + private static final Class ENTITY_CLASS = ActionsFailed.class; + + public void addActionsFailed(ActionsFailed actionsFailed) { + cassandraTemplate.insert(actionsFailed); + } + + public List getAllActionsFailed() { + + String selectAllQuery = "SELECT * FROM " + cassandraTemplate.getTableName(ENTITY_CLASS).toString() + ""; + + return (List) cassandraTemplate.select(selectAllQuery, ENTITY_CLASS); + } + + public boolean deleteActionsFailed(Set idsToDelete) { + + Query query = Query.query(Criteria.where("id").in(idsToDelete)); + return cassandraTemplate.delete(query, ENTITY_CLASS); + } +} diff --git a/src/main/java/biz/nynja/airdrop/repository/AirdropActionsRepository.java b/src/main/java/biz/nynja/airdrop/repository/AirdropActionsRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..31d606c95f87a6f70597c768a62c44a8e860502b --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/repository/AirdropActionsRepository.java @@ -0,0 +1,94 @@ +package biz.nynja.airdrop.repository; + +import biz.nynja.airdrop.entity.AirdropActions; +import com.datastax.driver.core.querybuilder.Batch; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import com.datastax.driver.core.querybuilder.Select; +import com.datastax.driver.core.querybuilder.Update; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.cassandra.core.CassandraTemplate; +import org.springframework.data.cassandra.core.query.Criteria; +import org.springframework.data.cassandra.core.query.Query; +import org.springframework.stereotype.Repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Repository +public class AirdropActionsRepository { + + @Autowired + private CassandraTemplate cassandraTemplate; + + private static final Class ENTITY_CLASS = AirdropActions.class; + + public List getAirDropActions(UUID accountId, String actionType) { + + Query query = (actionType == null) ? Query.query(Criteria.where("account_id").is(accountId)) : + Query.query(Criteria.where("action_type").is(actionType)).and(Criteria.where("account_id").is(accountId)); + + return (List) cassandraTemplate.select(query, ENTITY_CLASS); + } + + public void addAirdropActions(AirdropActions airdropActions) { + cassandraTemplate.insert(airdropActions); + } + + public Integer getLastCount(UUID accountId, String actionType) { + + Select.Where select = QueryBuilder.select().max("count").from(cassandraTemplate.getTableName(ENTITY_CLASS).toString()) + .where(QueryBuilder.eq("account_id", accountId)).and(QueryBuilder.eq("action_type", actionType)); + return cassandraTemplate.getCqlOperations().queryForObject(select, Integer.class); + + } + + + public boolean updateAirdropActions(UUID accountId, UUID id, String actionType, Map updateMap) { + + Update update = QueryBuilder.update(cassandraTemplate.getTableName(ENTITY_CLASS).toString()); + + for (String key : updateMap.keySet()) { + if (updateMap.get(key) != null) { + update.with(QueryBuilder.set(key, updateMap.get(key))); + } + } + + update.where(QueryBuilder.eq("account_id", accountId)).and(QueryBuilder.eq("id", id)) + .and(QueryBuilder.eq("action_type", actionType)); + + return cassandraTemplate.getCqlOperations().execute(update); + + } + + public boolean updateAirdropActionsBatch(Map updateMap, Map> whereClause) { + + List updates = new ArrayList<>(whereClause.size()); + for(UUID actionId : whereClause.keySet()){ + Update update = QueryBuilder.update(cassandraTemplate.getTableName(ENTITY_CLASS).toString()); + for (String key : updateMap.keySet()) { + if (updateMap.get(key)!= null) { + update.with(QueryBuilder.set(key, updateMap.get(key))); + } + } + + update.where(QueryBuilder.eq("id", actionId)) + .and(QueryBuilder.eq("account_id", whereClause.get(actionId).get("account_id"))) + .and(QueryBuilder.eq("action_type", whereClause.get(actionId).get("action_type"))); + + + updates.add(update); + + } + + Batch batch = QueryBuilder.batch(updates.get(0)); + for(int i = 1; i <= updates.size() - 1; i++){ + batch.add(updates.get(i)); + } + + return cassandraTemplate.getCqlOperations().execute(batch); + } + + +} diff --git a/src/main/java/biz/nynja/airdrop/repository/AirdropProfilesRepository.java b/src/main/java/biz/nynja/airdrop/repository/AirdropProfilesRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..265bbee2cf78048a8396a2a7c66778869debd3e2 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/repository/AirdropProfilesRepository.java @@ -0,0 +1,63 @@ +package biz.nynja.airdrop.repository; + +import biz.nynja.airdrop.entity.AirdropProfiles; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.cassandra.core.CassandraTemplate; +import org.springframework.data.cassandra.core.query.Criteria; +import org.springframework.data.cassandra.core.query.Query; +import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; + +@Repository +public class AirdropProfilesRepository { + + private static final Class ENTITY_CLASS = AirdropProfiles.class; + + @Autowired + private CassandraTemplate cassandraTemplate; + + public void addAirdropProfile(AirdropProfiles airdropProfiles) { + cassandraTemplate.insert(airdropProfiles); + } + + + public List getAirdropProfiles(UUID accountId, UUID id) { + + + Query query = (id != null) ? Query.query(Criteria.where("account_id").is(accountId)).and(Criteria.where("id").is(id)) + : Query.query(Criteria.where("account_id").is(accountId)); + + + return (List) cassandraTemplate.select(query, ENTITY_CLASS); + } + + + public AirdropProfiles getAirdropProfileByUserName(String username) { + + Query query = Query.query(Criteria.where("username").is(username)); + + return (AirdropProfiles) cassandraTemplate.selectOne(query, ENTITY_CLASS); + } + + public boolean updateAirdropProfile(UUID id, UUID accountId, Map updateMap) { + + com.datastax.driver.core.querybuilder.Update update = QueryBuilder.update(cassandraTemplate.getTableName(ENTITY_CLASS).toCql()); + + for (String key : updateMap.keySet()) { + if (updateMap.get(key) != null) { + update.with(QueryBuilder.set(key, updateMap.get(key))); + } + } + + update.where(eq("id", id)).and(eq("account_id", accountId)) + .ifExists(); + + return cassandraTemplate.getCqlOperations().execute(update); + + } + +} diff --git a/src/main/java/biz/nynja/airdrop/repository/OtpSessionsRepository.java b/src/main/java/biz/nynja/airdrop/repository/OtpSessionsRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..e38559b522e06efb44b70f6d44cd7cb0ef8f62ec --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/repository/OtpSessionsRepository.java @@ -0,0 +1,50 @@ +package biz.nynja.airdrop.repository; + +import biz.nynja.airdrop.entity.OtpSessions; +import com.datastax.driver.core.querybuilder.QueryBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.cassandra.core.CassandraTemplate; +import org.springframework.data.cassandra.core.query.Criteria; +import org.springframework.data.cassandra.core.query.Query; +import org.springframework.stereotype.Repository; + +import java.util.Map; +import java.util.UUID; + +import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; + +@Repository +public class OtpSessionsRepository { + + private static final Class ENTITY_CLASS = OtpSessions.class; + + @Autowired + private CassandraTemplate cassandraTemplate; + + public void addOtpSessions(OtpSessions otpSessions) { + cassandraTemplate.insert(otpSessions); + } + + public OtpSessions getOtpSessions(UUID id) { + Query query = Query.query(Criteria.where("id").is(id)); + return (OtpSessions) cassandraTemplate.selectOne(query, ENTITY_CLASS); + } + + public boolean updateOtpSessions(UUID id, UUID accountId, Map updateMap) { + + com.datastax.driver.core.querybuilder.Update update = QueryBuilder.update(cassandraTemplate.getTableName(ENTITY_CLASS).toCql()); + + for (String key : updateMap.keySet()) { + if (updateMap.get(key) != null) { + update.with(QueryBuilder.set(key, updateMap.get(key))); + } + } + + update.where(eq("id", id)).ifExists(); + + return cassandraTemplate.getCqlOperations().execute(update); + + } + + +} diff --git a/src/main/java/biz/nynja/airdrop/security/AuthorizationServerConfiguration.java b/src/main/java/biz/nynja/airdrop/security/AuthorizationServerConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..2c4e065e1f0c324d521aa1b251438bdaf8d87fe8 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/security/AuthorizationServerConfiguration.java @@ -0,0 +1,68 @@ +package biz.nynja.airdrop.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; + +@Configuration +@EnableAuthorizationServer +@ComponentScan(basePackages = "biz.nynja.airdrop.*") +public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { + + @Autowired + private PasswordEncoder passwordEncoder; + + + @Bean + protected TokenStore tokenStore() { + return new InMemoryTokenStore(); + } + + @Autowired + private TokenStore tokenStore; + + @Value("${users.qoinPro.client}") + private String client; + + @Value("${users.qoinPro.secret}") + private String secret; + + @Autowired + @Qualifier("authenticationManagerBean") + private AuthenticationManager authenticationManager; + + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + endpoints + .authenticationManager(authenticationManager) + .tokenStore(tokenStore); + } + + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients.inMemory() + .withClient(client) + .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit") + .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT", "USER") + .scopes("read", "write") + .autoApprove(true) + // .secret(passwordEncoder.encode("password")) + .accessTokenValiditySeconds(-1) + .secret(secret); + } + + +} diff --git a/src/main/java/biz/nynja/airdrop/security/ResourceServerConfiguration.java b/src/main/java/biz/nynja/airdrop/security/ResourceServerConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..348134fea48a201955c6f9b1b88ac0aa62147bee --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/security/ResourceServerConfiguration.java @@ -0,0 +1,26 @@ +package biz.nynja.airdrop.security; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; +import static biz.nynja.airdrop.constants.QoinProEndpoints.qoinProEndPoints; + +@Configuration +@ComponentScan("biz.nynja.airdrop.*") +@EnableResourceServer +public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { + + @Override + public void configure(HttpSecurity http) throws Exception { + + http.anonymous().disable() + .requestMatchers().antMatchers(qoinProEndPoints).and() + .authorizeRequests().antMatchers(qoinProEndPoints).hasRole("USER") + .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler()); + + + } +} diff --git a/src/main/java/biz/nynja/airdrop/security/WebSecurityConfiguration.java b/src/main/java/biz/nynja/airdrop/security/WebSecurityConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..b5f8dd48c66a7918bb42e8f186520a440bb887cc --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/security/WebSecurityConfiguration.java @@ -0,0 +1,55 @@ +package biz.nynja.airdrop.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +@EnableWebSecurity +@ComponentScan(basePackages = "biz.nynja.airdrop.*") +public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Autowired + private PasswordEncoder passwordEncoder; + + + @Value("${users.qoinPro.userName}") + private String username; + + @Value("${users.qoinPro.password}") + private String password; + + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + + @Autowired + protected void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception { + + // auth.inMemoryAuthentication().withUser("qoinPro").password(passwordEncoder.encode("test")).roles("USER"); + auth.inMemoryAuthentication().withUser(username).password(password).roles("USER"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + + http.csrf().disable() + .anonymous().disable().authorizeRequests() + .antMatchers("/airdrop/**").permitAll(); + //.antMatchers("/**").permitAll(); + } + + +} diff --git a/src/main/java/biz/nynja/airdrop/service/ActionSummaryService.java b/src/main/java/biz/nynja/airdrop/service/ActionSummaryService.java new file mode 100644 index 0000000000000000000000000000000000000000..8647bfd85af7ce3c9382e15444e5e1b0fb89e0d0 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/service/ActionSummaryService.java @@ -0,0 +1,18 @@ +package biz.nynja.airdrop.service; + +import biz.nynja.airdrop.entity.ActionsSummary; +import biz.nynja.airdrop.repository.ActionSummaryRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + + +@Service +public class ActionSummaryService { + + @Autowired + private ActionSummaryRepository actionSummaryRepository; + + public ActionsSummary getActionsSummary(String action) { + return actionSummaryRepository.getActionsSummary(action); + } +} \ No newline at end of file diff --git a/src/main/java/biz/nynja/airdrop/service/ActionsFailedService.java b/src/main/java/biz/nynja/airdrop/service/ActionsFailedService.java new file mode 100644 index 0000000000000000000000000000000000000000..17dfe193d2fa882a5eeca20634bdd21306df871a --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/service/ActionsFailedService.java @@ -0,0 +1,32 @@ +package biz.nynja.airdrop.service; + +import biz.nynja.airdrop.entity.ActionsFailed; +import biz.nynja.airdrop.repository.ActionsFailedRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +@Service +public class ActionsFailedService { + + @Autowired + private ActionsFailedRepository actionsFailedRepository; + + + public void addActionsFailed(ActionsFailed actionsFailed) { + actionsFailedRepository.addActionsFailed(actionsFailed); + } + + public List getAllActionsFailed() { + return actionsFailedRepository.getAllActionsFailed(); + } + + public boolean deleteActionsFailed(Set idsToDelete) { + return actionsFailedRepository.deleteActionsFailed(idsToDelete); + } + + +} diff --git a/src/main/java/biz/nynja/airdrop/service/AirdropActionsService.java b/src/main/java/biz/nynja/airdrop/service/AirdropActionsService.java new file mode 100644 index 0000000000000000000000000000000000000000..4f92815bd73fd72dffab0ba90e35bcd92c86f7d4 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/service/AirdropActionsService.java @@ -0,0 +1,40 @@ +package biz.nynja.airdrop.service; + +import biz.nynja.airdrop.entity.AirdropActions; +import biz.nynja.airdrop.repository.AirdropActionsRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Service +public class AirdropActionsService { + + @Autowired + AirdropActionsRepository airdropActionsRepository; + + public void addAirdropActions(AirdropActions airdropActions) { + airdropActionsRepository.addAirdropActions(airdropActions); + } + + public List getAirDropActions(UUID accountId, String actionType) { + return airdropActionsRepository.getAirDropActions(accountId, actionType); + } + + + public Integer getLastCount(UUID accountId, String actionType) { + return airdropActionsRepository.getLastCount(accountId, actionType); + } + + + public boolean updateAirdropActions(UUID accountId, UUID id, String actionType, Map updateMap) { + return airdropActionsRepository.updateAirdropActions(accountId, id, actionType, updateMap); + } + + public boolean updateAirdropActionsBatch(Map updateMap, Map> whereClause) { + return airdropActionsRepository.updateAirdropActionsBatch(updateMap, whereClause); + } + +} diff --git a/src/main/java/biz/nynja/airdrop/service/AirdropProfilesService.java b/src/main/java/biz/nynja/airdrop/service/AirdropProfilesService.java new file mode 100644 index 0000000000000000000000000000000000000000..0a84bb2584a9a77cd11b8bac8e6c96f6e9fd043c --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/service/AirdropProfilesService.java @@ -0,0 +1,38 @@ +package biz.nynja.airdrop.service; + +import biz.nynja.airdrop.entity.AirdropProfiles; +import biz.nynja.airdrop.repository.AirdropProfilesRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Service +public class AirdropProfilesService { + + @Autowired + private AirdropProfilesRepository airdropProfilesRepository; + + + + public void addAirdropProfile(AirdropProfiles airdropProfiles) { + airdropProfilesRepository.addAirdropProfile(airdropProfiles); + } + + public List getAirdropProfiles(UUID accountId, UUID id) { + + return airdropProfilesRepository.getAirdropProfiles(accountId, id); + } + + public AirdropProfiles getAirdropProfileByUserName(String username) { + return airdropProfilesRepository.getAirdropProfileByUserName(username); + } + + public boolean updateAirdropProfile(UUID id, UUID accountId, Map updateMap) { + return airdropProfilesRepository.updateAirdropProfile(id, accountId, updateMap); + } + + +} \ No newline at end of file diff --git a/src/main/java/biz/nynja/airdrop/service/OtpSessionsService.java b/src/main/java/biz/nynja/airdrop/service/OtpSessionsService.java new file mode 100644 index 0000000000000000000000000000000000000000..b61c664d904a441f664f58b56cf6bdf31dd75639 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/service/OtpSessionsService.java @@ -0,0 +1,28 @@ +package biz.nynja.airdrop.service; + +import biz.nynja.airdrop.entity.OtpSessions; +import biz.nynja.airdrop.repository.OtpSessionsRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.UUID; + +@Service +public class OtpSessionsService { + + @Autowired + private OtpSessionsRepository otpSessionsRepository; + + public void addOtpSessions(OtpSessions otpSessions) { + otpSessionsRepository.addOtpSessions(otpSessions); + } + + public OtpSessions getOtpSessions(UUID id) { + return otpSessionsRepository.getOtpSessions(id); + } + + public boolean updateOtpSessions(UUID id, UUID accountId, Map updateMap) { + return otpSessionsRepository.updateOtpSessions(id, accountId, updateMap); + } +} diff --git a/src/main/java/biz/nynja/airdrop/util/AirdropUtil.java b/src/main/java/biz/nynja/airdrop/util/AirdropUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..6aebeb287339a8f5df485b8595f10ca918a63544 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/util/AirdropUtil.java @@ -0,0 +1,22 @@ +package biz.nynja.airdrop.util; + + +import java.util.Random; + +public class AirdropUtil { + + // This function will generate random 4 digit OTP number + public static int generateOtpNumber() { + + Random r = new Random(); + String randomNumber = String.format("%04d", Integer.valueOf(r.nextInt(1001))); + return (int) (Math.random() * 9000) + 1000; + } + + + public static long generateCurrentEpochTimeStamp() { + return System.currentTimeMillis() / 1000; + } + + +} diff --git a/src/main/java/biz/nynja/airdrop/util/HttpConstants.java b/src/main/java/biz/nynja/airdrop/util/HttpConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..4b19fc8cfc534cce14d2a2fc9e58a586b6c92b92 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/util/HttpConstants.java @@ -0,0 +1,95 @@ +package biz.nynja.airdrop.util; + +import java.util.HashMap; +import java.util.Map; + +public class HttpConstants { + + // MESSAGE CODES + public interface MessageTags { + + //Message Codes + String INVALID_INPUT = "INVALID_INPUT"; + String DATA_NOT_FOUND = "DATA_NOT_FOUND"; + String AIRDROP_PROFILE_ALREADY_VERIFIED = "AIRDROP_PROFILE_ALREADY_VERIFIED"; + String AIRDROP_PROFILE_VERIFIED = "AIRDROP_PROFILE_VERIFIED"; + String AIRDROP_PROFILE_NOT_EXISTS = "AIRDROP_PROFILE_NOT_EXISTS"; + String OTP_NOT_MATCHED = "OTP_NOT_MATCHED"; + String OTP_SESSION_NOT_EXISTS = "OTP_SESSION_NOT_EXISTS"; + String INVALID_OTP_SESSION = "INVALID_OTP_SESSION"; + String OTP_EXPIRED = "OTP_EXPIRED"; + String OTP_IS_VERIFIED = "OTP_IS_VERIFIED"; + String OTP_IS_ALREADY_VERIFIED = "OTP_IS_ALREADY_VERIFIED"; + String USERNAME_IS_BLANK = "USERNAME_IS_BLANK"; + String INVALID_KAFKA_ACTION_TYPE = "INVALID_KAFKA_ACTION_TYPE"; + String NO_ACTION_EVENT_FOUND = "NO_ACTION_EVENT_FOUND"; + String OTP_IS_NOT_VALID = "OTP_IS_NOT_VALID"; + String FAILED_ACTIONS_NOTIFIED_SUCCESSFULLY = "FAILED_ACTIONS_NOTIFIED_SUCCESSFULLY"; + String NO_ACTION_TO_NOTIFY = "NO_ACTION_TO_NOTIFY"; + String UNABLE_TO_NOTIFY_QOIN_PRO = "UNABLE_TO_NOTIFY_QOIN_PRO"; + + } + + + // MessageTags - MessageBody Map + public static Map messages = new HashMap(14); + public static Map messageCodes = new HashMap(15); + + static { + + // Message Tag - Messages + messages.put(MessageTags.INVALID_INPUT, "Your input is not valid."); + messages.put(MessageTags.DATA_NOT_FOUND, "No results found."); + messages.put(MessageTags.AIRDROP_PROFILE_NOT_EXISTS, "Profile not exists."); + messages.put(MessageTags.OTP_NOT_MATCHED, "Otp not matched."); + messages.put(MessageTags.OTP_SESSION_NOT_EXISTS, "No such otp session exists."); + messages.put(MessageTags.OTP_EXPIRED, "Otp is expired."); + messages.put(MessageTags.OTP_IS_VERIFIED, "Otp is verified."); + messages.put(MessageTags.OTP_IS_ALREADY_VERIFIED, "Otp is already verified."); + messages.put(MessageTags.USERNAME_IS_BLANK, "Username is Blank."); + messages.put(MessageTags.INVALID_KAFKA_ACTION_TYPE, "Action Type is not valid."); + messages.put(MessageTags.NO_ACTION_EVENT_FOUND, "No actions found."); + messages.put(MessageTags.OTP_IS_NOT_VALID, "OTP is not valid."); + messages.put(MessageTags.AIRDROP_PROFILE_VERIFIED, "Profile is verified."); + messages.put(MessageTags.INVALID_OTP_SESSION, "OTP Session is not correct."); + messages.put(MessageTags.FAILED_ACTIONS_NOTIFIED_SUCCESSFULLY, "Actions are successfully notified to QoinPro."); + messages.put(MessageTags.NO_ACTION_TO_NOTIFY, "No action exists to notify."); + messages.put(MessageTags.AIRDROP_PROFILE_ALREADY_VERIFIED, "Profile is already verified."); + messages.put(MessageTags.UNABLE_TO_NOTIFY_QOIN_PRO, "Qoinpro couldn't be notified."); + + // Message Code - Messages + messageCodes.put(MessageTags.INVALID_INPUT, (short) 1); + messageCodes.put(MessageTags.DATA_NOT_FOUND, (short) 2); + messageCodes.put(MessageTags.AIRDROP_PROFILE_NOT_EXISTS, (short) 3); + messageCodes.put(MessageTags.OTP_NOT_MATCHED, (short) 4); + messageCodes.put(MessageTags.OTP_SESSION_NOT_EXISTS, (short) 5); + messageCodes.put(MessageTags.OTP_EXPIRED, (short) 6); + messageCodes.put(MessageTags.OTP_IS_VERIFIED, (short) 7); + messageCodes.put(MessageTags.OTP_IS_ALREADY_VERIFIED, (short) 8); + messageCodes.put(MessageTags.USERNAME_IS_BLANK, (short) 9); + messageCodes.put(MessageTags.INVALID_KAFKA_ACTION_TYPE, (short) 10); + messageCodes.put(MessageTags.NO_ACTION_EVENT_FOUND, (short) 11); + messageCodes.put(MessageTags.OTP_IS_NOT_VALID, (short) 12); + messageCodes.put(MessageTags.AIRDROP_PROFILE_VERIFIED, (short) 13); + messageCodes.put(MessageTags.INVALID_OTP_SESSION, (short) 14); + messageCodes.put(MessageTags.FAILED_ACTIONS_NOTIFIED_SUCCESSFULLY, (short) 15); + messageCodes.put(MessageTags.NO_ACTION_TO_NOTIFY, (short) 16); + messageCodes.put(MessageTags.AIRDROP_PROFILE_ALREADY_VERIFIED, (short) 17); + messageCodes.put(MessageTags.UNABLE_TO_NOTIFY_QOIN_PRO, (short) 18); + } + + + public interface statusCode { + + // HTTP RESPONSE STATUS CODE + int SUCCESS_STATUS_CODE = 200; + int INTERNAL_SERVER_ERROR_STATUS_CODE = 500; + int CONTENT_NOT_FOUND_STATUS_CODE = 204; + int FORBIDDEN_STATUS_CODE = 403; + int UNAUTHORIZED_STATUS_CODE = 401; + int BAD_REQUEST_STATUS_CODE = 400; + int SERVICE_UNAVAILABLE_STATUS_CODE = 502; + + } + +} diff --git a/src/main/java/biz/nynja/airdrop/util/HttpResponse.java b/src/main/java/biz/nynja/airdrop/util/HttpResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..19d126e15a065809da71a65b190920be44f5eeab --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/util/HttpResponse.java @@ -0,0 +1,31 @@ +package biz.nynja.airdrop.util; + +import java.util.HashMap; +import java.util.Map; + +import static biz.nynja.airdrop.util.HttpConstants.*; + +public class HttpResponse { + + + //public static Map getResponse(int statusCode, Map data, String messageTag) { + public static Map getResponse(int statusCode, Object data, String messageTag) { + + + Map response = new HashMap(4); + response.put("status", statusCode); + + if (data != null) { + response.put("data", data); + } + if (messageTag != null) { + response.put("message", messages.get(messageTag)); + response.put("message_code", messageCodes.get(messageTag)); + } + + return response; + + } + + +} diff --git a/src/main/java/biz/nynja/airdrop/util/RestTemplateUtil.java b/src/main/java/biz/nynja/airdrop/util/RestTemplateUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..d06aafc0f74681be71fac856d30d9273df4f5497 --- /dev/null +++ b/src/main/java/biz/nynja/airdrop/util/RestTemplateUtil.java @@ -0,0 +1,87 @@ +package biz.nynja.airdrop.util; + +import biz.nynja.airdrop.constants.ServiceAPIs; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +@Component +public class RestTemplateUtil { + + @Value("${rest.pushService.address}" + ServiceAPIs.pushService.pushNotification) + private String pushNotification; + + @Value("${rest.qoinPro.address}" + ServiceAPIs.qoinPro.channelEngagement) + private String qoinProChannelEngagement; + + @Value("${authentication.qoinPro.header.name}") + private String quionProAuthenticationHeaderName; + + @Value("${authentication.qoinPro.header.value}") + private String quionProAuthenticationHeaderValue; + + + private static final Logger logger = LoggerFactory.getLogger(RestTemplateUtil.class); + + @Autowired + private RestTemplate restTemplate; + + public String qoinProChannelEngagementAPI(String userId, String username, String actionType, String actionDescription) { + + MultiValueMap params = new LinkedMultiValueMap<>(5); + params.add("userId", userId); + params.add("userName", username); + params.add("action", actionType); + params.add("channel", "nynja"); + params.add("actionDetail", actionDescription); + + HttpHeaders tokenHeader = new HttpHeaders(); + tokenHeader.add(quionProAuthenticationHeaderName, quionProAuthenticationHeaderValue); + + HttpEntity> httpEntity = new HttpEntity(params, tokenHeader); + + // In case of success. its response will be "Success" + try { + Object response = restTemplate.postForObject(qoinProChannelEngagement, httpEntity, Object.class); + logger.info("QoinPro action API response : {}", response); + + + return response.toString(); + + } catch (Exception e) { + logger.info("Exception in calling action API of quionPro service. message: {}, cause: {}", e.getMessage(), e.getCause()); + e.getStackTrace(); + + return "Failed"; + } + + } + + + public void sendPushNotification(String accountId, int otp) { + + MultiValueMap pushNotificationParams = new LinkedMultiValueMap(2); + pushNotificationParams.add("account_id", accountId); + pushNotificationParams.add("otp", otp); + + HttpEntity> httpEntity = new HttpEntity(pushNotificationParams, new HttpHeaders()); + + boolean response = restTemplate.postForObject(pushNotification, httpEntity, Boolean.class); + + logger.info("Send Push Notification API Response: {}", response); + + + /* System.out.println(""); + System.out.println("SendPushNotificationResponse : " + response); + System.out.println("");*/ + + } + +} diff --git a/src/main/proto/account.proto b/src/main/proto/account.proto new file mode 100644 index 0000000000000000000000000000000000000000..95f38a72c9eef3d3e5753e1ad8be62033a2f3bfb --- /dev/null +++ b/src/main/proto/account.proto @@ -0,0 +1,446 @@ +// +// proto3 +// Syntax documentation - see https://developers.google.com/protocol-buffers/docs/proto3 +// +syntax = "proto3"; + +// GRPC package, also used by the Go code generator +package account; + +option java_generic_services = true; +option java_multiple_files = true; +option java_package = "biz.nynja.account.grpc"; +option java_outer_classname = "Account"; + +service AccountService { + /* This must be called only internally by auth service. Country selector must be in uppercase (BG:). + Roles: ACCOUNT_ADMIN, AUTH_SERVICE, USER. */ + rpc createPendingAccount(CreatePendingAccountRequest) returns (CreatePendingAccountResponse); + + /* Must be called only once when a user logs in for first time. + Roles: USER.*/ + rpc completePendingAccountCreation(CompletePendingAccountCreationRequest) returns (AccountResponse); + + /* Return all accounts with specified profile Id. + Roles: ACCOUNT_ADMIN.*/ + rpc getAllAccountsByProfileId(AccountsByProfileIdRequest) returns (AccountsResponse); + + /* Return the account specified by profile Id. + Roles: ACCOUNT_ADMIN, AUTH_SERVICE, USER.*/ + rpc getAccountByAccountId(AccountByAccountIdRequest) returns (AccountResponse); + + /* Return the account specified by username. + Roles: ACCOUNT_ADMIN, USER.*/ + rpc getAccountByUsername(GetByUsernameRequest) returns (AccountResponse); + + /* Return the account specified by QR Code. + Roles: ACCOUNT_ADMIN, USER.*/ + rpc getAccountByQrCode(GetByQrCodeRequest) returns (AccountResponse); + + /* Return the account specified by the authentication provider(phone, email etc.) used as creation provider for the account. + Roles: ACCOUNT_ADMIN, AUTH_SERVICE, USER.*/ + rpc getAccountByCreationProvider(AuthenticationProviderRequest) returns (AccountResponse); + + /* Updates specified account + Roles: ACCOUNT_ADMIN, USER.*/ + rpc updateAccount (UpdateAccountRequest) returns (AccountResponse); + + /* Delete specified account + Roles: ACCOUNT_ADMIN,USER.*/ + rpc deleteAccount (DeleteAccountRequest) returns (StatusResponse); + + /* Delete specified profile + Roles: ACCOUNT_ADMIN, USER.*/ + rpc deleteProfile (DeleteProfileRequest) returns (StatusResponse); + + /* Return the profile specified by profile Id. + Roles: ACCOUNT_ADMIN, USER.*/ + rpc getProfileByProfileId (ProfileByProfileIdRequest) returns (ProfileResponse); + + /* Add specified Authentication provider. To one profile could be added maximum three authentication providers. + If searchable option is not set or SEARCH_NOT_SET is selected, the default: SEARCH_ENABLED will be used instead. + Roles: ACCOUNT_ADMIN, AUTH_SERVICE, USER*/ + rpc addAuthenticationProviderToProfile(AddAuthenticationProviderRequest) returns (StatusResponse); + + /* Delete authentication Provider. The last authentication provider could not be deleted. + Roles: ACCOUNT_ADMIN, USER. */ + rpc deleteAuthenticationProviderFromProfile(DeleteAuthenticationProviderRequest) returns (StatusResponse); + + /* Update specified Authentication provider. The operation is currently not supported. + Roles: ACCOUNT_ADMIN, AUTH_SERVICE, USER.*/ + rpc updateAuthenticationProviderForProfile(UpdateAuthenticationProviderRequest) returns (StatusResponse); + + /* Add Contact information which will be visible for the other Nynja users. + Roles: ACCOUNT_ADMIN, USER*/ + rpc addContactInfoToAccount(AddContactInfoRequest) returns (StatusResponse); + + /* Delete Contact information. + Roles: ACCOUNT_ADMIN, USER*/ + rpc deleteContactInfoFromAccount(DeleteContactInfoRequest) returns (StatusResponse); + + /* Edit Contact information. + Roles: ACCOUNT_ADMIN, USER. */ + rpc editContactInfoForAccount(EditContactInfoRequest) returns (StatusResponse); + + /* Search account by username. This is pretty much like getAccountByUsername but the result does not contain all account fields + Roles: ANY. */ + rpc searchByUsername(GetByUsernameRequest) returns (SearchResponse); + + /* Search account by phone number. This is pretty much like getAccountByCreationProvider but the result does not contain all account fields + Roles: ANY. */ + rpc searchByPhoneNumber(GetByPhoneNumberRequest) returns (SearchResponse); + + /* Search account by email. This is pretty much like getAccountByCreationProvider but the result does not contain all account fields + Roles: ANY.*/ + rpc searchByEmail(GetByEmailRequest) returns (SearchResponse); + + /* Search account by QRCode. This is pretty much like getAccountByQrCode but the result does not contain all account fields + Roles: ANY. */ + rpc searchByQrCode(GetByQrCodeRequest) returns (SearchResponse); + + /* Return account by the authentication provider(phone, email etc.) used as login option. + Roles: ACCOUNT_ADMIN, AUTH_SERVICE, USER.*/ + rpc getAccountByLoginOption(AuthenticationProviderRequest) returns (AccountResponse); + + /* Search account by social provider. This is pretty much like getAccountByLoginOption but the search is limited to authenticationType social provider and the result does not contain all account fields. + Roles: ANY.*/ + rpc searchBySocialProvider(AuthenticationProviderRequest) returns (SearchResponse); + + /* Update searchable option for a given authentication provider. If searchable option is not set or SEARCH_NOT_SET is selected, the default: SEARCH_ENABLED will be used instead. + Roles: ANY.*/ + rpc updateSearchableOption(UpdateSearchableOptionRequest) returns (StatusResponse); + + /* Deletes avatar of account by accountId. + Roles: ACCOUNT_ADMIN, USER.*/ + rpc deleteAccountAvatar(DeleteAccountAvatarRequest) returns (StatusResponse); +} + +enum AuthenticationType { + MISSING_TYPE = 0; + PHONE = 1; + EMAIL = 2; + FACEBOOK = 3; + GOOGLEPLUS = 4; +} + +enum ContactType { + MISSING_CONTACT_TYPE = 0; + PHONE_CONTACT = 1; + EMAIL_CONTACT = 2; + FACEBOOK_CONTACT = 3; + GOOGLEPLUS_CONTACT = 4; + TWITTER_CONTACT = 5; +} + +/* Each user could have one or more roles listed bellow: */ +enum Role { + UNKNOWN_ROLE = 0; + USER = 1; // regular user + ACCOUNT_ADMIN = 2; // this role must be treated as account admin + AUTH_SERVICE = 3; // role used by the authentication service + AUTHENTICATION_ADMIN = 4; // this role must be treated as authentication admin +} + +/* Used for setting the authentication provider's searchable option */ +enum SearchableOption { + SEARCH_NOT_SET = 0; // SEARCH_NOT_SET is for endpoints where the search option is not used + SEARCH_ENABLED = 1; // enable search by the given authentication provider + SEARCH_DISABLED = 2; // disable search by the given authentication provider +} + +enum AccessStatus { + UNKNOWN_ACCESS_STATUS = 0; + ENABLED = 1; // Regular running user. + SUSPENDED = 2; // Behaves exactly as deleted for certain period. + DISABLED = 3; // All requests from such user are discarded including login. +} +/*Request for creating an account by phone number, email address, etc.*/ +message CreatePendingAccountRequest { + AuthenticationType authenticationType = 1; // authentication type like phone, email etc. + string authenticationProvider = 2; // authentication provider value. If for example authenticationType is Phone country selector must be in uppercase (BG:) +} + +message CompletePendingAccountCreationRequest { + string accountId = 1; // required parameter + string avatar = 2; + string accountMark = 3; + string accountName = 4; // required parameter for Multiple accounts + string firstName = 5; // required parameter + string lastName = 6; + string username = 7; // optional parameter but globally unique + string qrCode = 9; + repeated Role roles = 11; // by default 'USER' + AccessStatus accessStatus = 12; // by default 'ENABLED' +} + +message AccountsByProfileIdRequest { + string profileId = 1; +} + +/* Request for updating searchable option. */ +message UpdateSearchableOptionRequest { + string profileId = 1; // ID of the profile + string authenticationType = 2; // Authentication type + string authenticationIdentifier = 3; // Authentication identifier as stored in the database + SearchableOption searchOption = 4; //The search option to use. If it is not set or SEARCH_NOT_SET is selected, the default: SEARCH_ENABLED will be used instead. +} + +message Date { + int32 year = 1; // year in format YYYY i.e 2019 + int32 month = 2; //month: from 1(January) to 12(December) + int32 day = 3; // valid day with value from 1 to 31 +} + +message AccountByAccountIdRequest { + string accountId = 1; +} + +message ProfileByProfileIdRequest { + string profileId = 1; // Must be valid UUID +} + +message AuthenticationProviderRequest { + AuthenticationType authenticationType = 1; // Can be PHONE, EMAIL, FB, GOOGLE+ + string authenticationIdentifier = 2; // The actual value to search for, like the phone number, + // email address, Facebook user Id, Google user Id +} + +message UpdateAccountRequest { + string accountId = 1; + string avatar = 2; + string accountMark = 3; + string accountName = 4; + string firstName = 5; + string lastName = 6; + string username = 7; + repeated Role roles = 10; + AccessStatus accessStatus = 11; + Date birthday = 12; //To remove the value for birthday date do not set the birthday field or use 0,0,0 for year, month, day. +} + +message DeleteAccountRequest { + string accountId = 1; +} + +message DeleteAccountAvatarRequest { + string accountId = 1; +} + +message DeleteProfileRequest { + string profileId = 1; +} + +message AddAuthenticationProviderRequest { + string profileId = 1; + AuthProviderDetails authenticationProvider = 2; +} + +message AddContactInfoRequest { + string accountId = 1; + ContactDetails contactInfo = 2; +} + +message DeleteAuthenticationProviderRequest { + string profileId = 1; + AuthProviderDetails authenticationProvider = 2; +} + +message UpdateAuthenticationProviderRequest { + string profileId = 1; + AuthProviderDetails oldAuthProvider = 2; + AuthProviderDetails updatedAuthProvider = 3; +} + +message DeleteContactInfoRequest { + string accountId = 1; + ContactDetails contactInfo = 2; +} + +message EditContactInfoRequest { + string accountId = 1; + ContactDetails oldContactInfo = 2; + ContactDetails editedContactInfo = 3; +} + +message PendingAccountDetails { + string accountId = 1; +} + +message CreatePendingAccountResponse { + uint64 requestId = 1; + oneof result { + ErrorResponse error = 2; + PendingAccountDetails pendingAccountDetails = 3; + } +} + +message AuthProviderDetails { + AuthenticationType authenticationType = 1; // Request for creating an account by phone number, email address, etc. + string authenticationProvider = 2; // the actual value i.e. phone number when authenticationType is PHONE etc. + SearchableOption searchOption = 3; // Used to specify the authentication provider's searchable option. +} + +message ContactDetails { + ContactType type = 1; + string value = 2; // depends on the contact type. For example phone number when type is PHONE etc. + /* The label is not mandatory. It could be used only when type field is set to 'PHONE_CONTACT'. + * The value of this label should clarify the phone number: for example "work", "home" etc. */ + string label = 3; + string countrySelector = 4; // This is country selector for phone number contact info (selectors like BG, US and so on) +} + +message AccountDetails { + string accountId = 1; // account id + string profileId = 2; // profile id + string authenticationIdentifier = 3; + string authenticationType = 4; + string avatar = 5; // Valid URL which point to the avatar image. Not mandatory. + string accountMark = 6; // Not used for now. Will be used for multiple accounts. + string accountName = 7; // Not used for now. Will be used for multiple accounts. + string firstName = 8; // User's first name + string lastName = 9; // User's last name + string username = 10; // Unique identifier + string qrCode = 12; // Unique identifier that is used for generation of the QR + repeated ContactDetails contactsInfo = 14; // Contacts that belong to the account + repeated Role roles = 15; // Array of Roles that belongs to the account + AccessStatus accessStatus = 16; + Date birthday = 17; + int64 creationTimestamp = 18; // Creation timestamp is set when account is created. + int64 lastUpdateTimestamp = 19; // Update timestamp is '0' if account is never updated. +} + +message AccountResponse { + uint64 requestId = 1; + oneof result { + ErrorResponse error = 2; + AccountDetails accountDetails = 3; + } +} + +//PREVIOUSLY RESERVED FIELD NUMBERS: 3 +//NOT to be used for newly added fields. +message ProfileDetails { + string profileId = 1; + repeated AuthProviderDetails authProviders = 2; + string passcode = 4; // Not used for now. Will be used when multiple accounts are implemented + string defaultAccountId = 5; // Not used for now. Will be used when multiple accounts are implemented +} + +message ProfileResponse { + uint64 requestId = 1; + oneof result { + ErrorResponse error = 2; + ProfileDetails profileDetails = 3; + } +} + +message StatusResponse { + uint64 requestId = 1; + oneof result { + ErrorResponse error = 2; + string status = 3; + } +} + +message AccountsList { + repeated AccountDetails accountDetails = 1; +} + +message AccountsResponse { + uint64 requestId = 1; + oneof result { + ErrorResponse error = 2; + AccountsList accountsResponse = 3; + } +} + +message GetByUsernameRequest { + string username = 1; +} + +message GetByPhoneNumberRequest { + string phoneNumber = 1; +} + +message GetByEmailRequest { + string email = 1; +} + +message GetByQrCodeRequest { + string qrCode = 1; +} + +message SearchResponse { + uint64 requestId = 1; + oneof result { + ErrorResponse error = 2; + SearchResultDetails searchResultDetails = 3; + } +} + +message SearchResultDetails { + string accountId = 1; + string avatar = 2; + string firstName = 3; + string lastName = 4; +} + +message ErrorResponse { + enum Cause { + INTERNAL_SERVER_ERROR = 0; + MISSING_ACCOUNT_ID = 1; //account id is mandatory but not set in the request + INVALID_ACCOUNT_ID = 2; // Passed account id has invalid format. Possible formats ? + INVALID_ACCOUNT_NAME = 3; // Passed account name has invalid format. Possible formats ? + ACCOUNT_NOT_FOUND = 5; // specified account is not found + MISSING_PROFILE_ID = 6; //profile id is mandatory but not set in the request + INVALID_PROFILE_ID = 7; // Passed first name has invalid format. Possible formats ? + PROFILE_NOT_FOUND = 8; // specified profile is not found + MISSING_AUTH_PROVIDER_TYPE = 9; //auth provider is mandatory but not set in the request + MISSING_AUTH_PROVIDER_ID = 10; //auth provider identifier is mandatory but not set in the request + INVALID_AUTH_PROVIDER_TYPE = 11; // Passed username has invalid type. Look at @AuthenticationType enum. + AUTH_PROVIDER_ALREADY_USED = 12; // Passed authentication provider is already used by another account + AUTH_PROVIDER_NOT_FOUND = 13; // Passed authentication provider is not found + MISSING_FIRST_NAME = 14; //first name is mandatory but not set in the request + INVALID_FIRST_NAME = 15; // Passed first name has invalid format. Possible formats ? + INVALID_LAST_NAME = 16; // Passed last name has invalid format. Possible formats ? + MISSING_EMAIL = 17; //email is mandatory but not set in the request + INVALID_EMAIL = 18; //email syntax is not correct. + EMAIL_ALREADY_USED = 19; // exists account which already use this email. + MISSING_USERNAME = 21; //username is mandatory but not set in the request + USERNAME_ALREADY_USED = 23; // exists account which already use this username. + INVALID_USERNAME = 24; // Passed username has invalid format. Possible formats ? + MISSING_PHONENUMBER = 25; //phone number is mandatory but not set in the request + INVALID_PHONENUMBER = 26; // Passed phone number has invalid format. Possible formats :+ or +. For Example BG:+359878123456 or +359878123456 + PHONENUMBER_ALREADY_USED = 28; // exists account which already use this phone number. + MISSING_QR_CODE = 29; //QR code is mandatory but not set in the request + INVALID_QR_CODE = 30; // Passed QR code has invalid format. Possible formats ? + MISSING_CONTACT_INFO_TYPE = 32; //contact info is mandatory but not set in the request + MISSING_CONTACT_INFO_ID = 33; //contact info identifier is mandatory but not set in the request + INVALID_BIRTHDAY_DATE = 34; // Passed birthday has invalid format. Possible formats ? + MULTIPLE_INVALID_PARAMETERS = 35; // several input fields are not valid + INVALID_ACCESS_STATUS = 36; // Passed access status has invalid value/type. Refer @AccessStatus enum + ERROR_CREATING_ACCOUNT = 37; // error occurred during account creation + ERROR_UPDATING_ACCOUNT = 38; // error occurred during account update + ERROR_DELETING_ACCOUNT = 39; // error occurred during account deletion + ERROR_UPDATING_PROFILE = 40; // error occurred during profile update + ERROR_DELETING_PROFILE = 41; // error occurred during profile deletion + ERROR_ADDING_AUTH_PROVIDER = 42; // error occurred authentication provider addition + ERROR_UPDATING_AUTH_PROVIDER = 43; + ERROR_DELETING_AUTH_PROVIDER = 44; // error occurred during authentication provider deletion + ERROR_ADDING_CONTACT_INFO = 45; // error occurred during contact info addition + ERROR_DELETING_CONTACT_INFO = 46; // error occurred during contact info removal + ERROR_EDITING_CONTACT_INFO = 47; // error occurred during contact info update + ERROR_PERMISSION_DENIED = 48; // error when accessing an endpoint/rpc without required permissions + FACEBOOK_GOOGLEPLUS_ONLY_EXPECTED = 49; // error when searching by social provider other than FACEBOOK and GOOGLEPLUS + MAX_CONTACT_INFO_OF_TYPE_REACHED = 50; // error when the max count of contact info of given type is reached + INVALID_PHONE_LABEL = 51; // invalid label used for PHONE_CONTACT in contact info + INVALID_ADDITIONAL_LOGIN_OPTION_TYPE = 52; // the requested type can not be used for additional login option + ADMIN_INVALID_START_ROW = 53; // ADMIN AG-GRID start row is invalid. Should be more than 1 + ADMIN_INVALID_END_ROW = 54; // ADMIN AG-GRID end row is invalid. Should be more than start row and less than current max of 100 + ERROR_UPDATING_SEARCHABLE_OPTION = 55; // error occurred during setting searchable option + MAX_PROVIDERS_PER_PROFILE_REACHED = 56; // Maximum number of authentication providers per profile reached for this profile + ADMIN_INVALID_FILTER_CRITERIA = 57; // Invalid search criteria type + } + Cause cause = 1; + string message = 2; +} \ No newline at end of file diff --git a/src/main/proto/applicationService.proto b/src/main/proto/applicationService.proto new file mode 100644 index 0000000000000000000000000000000000000000..6bccbbb6506b18351237ebb21ab0842635cc614b --- /dev/null +++ b/src/main/proto/applicationService.proto @@ -0,0 +1,21 @@ +syntax ="proto3"; +option java_multiple_files = true; + +package org.grpc.service; + +message verifyAccountRequest { + string username = 1; + +} + +message verifyAccountResponse { + int32 status = 2; + string accountId = 3; +} + + +service verifyAccountService { + rpc verifyAccount(verifyAccountRequest) returns (verifyAccountResponse); +} + + diff --git a/src/main/proto/auth.proto b/src/main/proto/auth.proto new file mode 100644 index 0000000000000000000000000000000000000000..177efb45cabd61a1adefba463ebb73a818428086 --- /dev/null +++ b/src/main/proto/auth.proto @@ -0,0 +1,305 @@ +// +// proto3 +// Syntax documentation - see https://developers.google.com/protocol-buffers/docs/proto3 +// +syntax = "proto3"; + +// GRPC package, also used by the Go code generator +package authentication; + +option java_generic_services = true; +option java_multiple_files = true; +option java_package = "biz.nynja.authentication.grpc"; +option java_outer_classname = "Authentication"; + +service AuthenticationService { + /* Rerieves an auth token based on request items. + Accessible without access token. */ + rpc generateAuthToken(GenerateAuthTokenRequest) returns (GenerateTokenResponse); + + /* Rerieves a verify token based on request items. + Accessible without access token. */ + rpc generateVerifyToken(GenerateVerifyTokenRequest) returns (GenerateTokenResponse); + + /* Rerieves an access token based on request items. + Accessible without access token. */ + rpc generateAccessToken(GenerateAccessTokenRequest) returns (GenerateAccessTokenResponse); + + /*Provide admin token for authorized users. + Roles: USER. */ + rpc exchangeRefreshToken(ExchangeRefreshTokenRequest) returns (GenerateTokenResponse); + + /*Add/update auth provider for a user. + Roles: USER. */ + rpc verifyAuthProvider(VerifyAuthProviderRequest) returns (StatusResponse); + + /* Exchanges the attached metadata accessToken property for an admin token. + Roles: USER. */ + rpc generateAdminAccessToken(EmptyRequest) returns (GenerateTokenResponse); + + /* Returns all accesspoints for specified account Id. + Roles: AUTHENTICATION_ADMIN, USER. */ + rpc getAllAccessPointsForAccount(AccessPointsByAccountIdRequest) returns (AccessPointsResponse); + + /* Returns the accesspoint specified by account Id and access token. + Roles: AUTHENTICATION_ADMIN, USER. */ + rpc getAccessPoint(AccessPointByAccountIdAndAccessTokenRequest) returns (AccessPointResponse); + + /* Deletes all accesspoints for specified by account Id. + Roles: AUTHENTICATION_ADMIN, USER. */ + rpc deleteAccessPointsForAccount(AccessPointsByAccountIdRequest) returns (StatusResponse); + + /* Deletes accesspoint specified by account Id and access token. + Roles: AUTHENTICATION_ADMIN, USER. */ + rpc deleteAccessPoint(AccessPointByAccountIdAndAccessTokenRequest) returns (StatusResponse); + + /* Returns an accesspoint or error if not found specified by access token. + Roles: AUTHENTICATION_ADMIN. */ + rpc getAccessPointByAccessToken(AccessPointByAccessTokenRequest) returns (AccessPointResponse); +} + +message GenerateAuthTokenRequest { + string instanceId = 2; // The instance_id value is the unique identifier for the installation of the app on the device created as a random 128-bit value and base64 encoded. + string appClass = 3; // The app_class value uniquely identifies the application requesting the token that is independent of how it is published such that the same application published for iOS or Android or for another organization has the same app_class even though it is published with different application identifiers. + string orgId = 4; // The org_id value uniquely identifies the tenant organization requesting the token. When absent, a default organization (Nynja) is assumed. +} + +message GenerateVerifyTokenRequest { + string sid = 1; //sign identifier( phone number or email ) + SidType sidType = 2; // sign type : phone or email. + enum SendMethod { + IDLE = 0; + SMS = 1; // use sms to send verify code + CALL = 2; //use voice call to send verify code + } + SendMethod sendVia = 3; +} + +message EmptyRequest { +} + +message GenerateAccessTokenRequest { + string instanceId = 2; // The instance_id value is the unique identifier for the installation of the app on the device created as a random 128-bit value and base64 encoded. + string appClass = 3; // The app_class value uniquely identifies the application requesting the token that is independent of how it is published such that the same application published for iOS or Android or for another organization has the same app_class even though it is published with different application identifiers. + string orgId = 4; // The org_id value uniquely identifies the tenant organization requesting the token. When absent, a default organization (Nynja) is assumed. + SidType sidType = 5;// sign type : phone or email, google, facebook. + string verifyToken = 6; // mandatory only for email and phone + string loginCode = 7; // mandatory only for for email and phone + string socialToken = 8; // mandatory only for google and facebook + string deviceId = 9; // device description + enum ClientType { + UNKNOWN_CLIENT_TYPE = 0; + WEB_CLIENT = 1; + ANDROID_CLIENT = 2; + IOS_CLIENT = 3; + } + // Required for Google Login. ClientType is enum and can be WEB/ANDROID/IOS or not set, it's used to identify the type of a client for things like redirectUri settings etc. + ClientType clientType = 10; +} + +message ExchangeRefreshTokenRequest { + string refreshToken = 1; // refresh token returned during login process +} + +enum SidType { + NOT_SET = 0; + PHONE = 1; + EMAIL = 2; + FACEBOOK = 3; + GOOGLEPLUS = 4; +} + +message VerifyAuthProviderRequest { + string verifyToken = 1; //mandatory, returned by generateVerifyToken rpc + string verifyCode=2; // mandatory, send to the user via sms or email + string profileId=3; //mandatory, the authprovider will be added this profile. + enum Action { + ACTION_NOT_SET = 0; + ACTION_ADD_AUTH_PROVIDER = 1; + ACTION_UPDATE_AUTH_PROVIDER = 2; //this action is currently not supported + } + //sidType of the auth provider to be removed; used with action: ACTION_UPDATE_AUTH_PROVIDER. ACTION_UPDATE_AUTH_PROVIDER is currently not supported + SidType sidTypeToRemove = 4; + //auth provider to be removed; used with action: ACTION_UPDATE_AUTH_PROVIDER. ACTION_UPDATE_AUTH_PROVIDER is currently not supported + string sidToRemove = 5; + //action: for adding/updating auth provider. Updating auth provider is currently not supported + Action action = 6; +} + +message AccessPointsByAccountIdRequest { + string accountId = 1; +} + +message AccessPointByAccountIdAndAccessTokenRequest { + string accountId = 1; + string accessToken = 2; //valid access token/JWT/ +} + +message AccessPointByAccessTokenRequest { + string accessToken = 1; // valid access token, created by authentication service during log-in flow. +} + +message GenerateTokenResponse { + oneof result { + ErrorResponse error = 1; + TokenResponseDetails tokenResponseDetails = 2; + } +} + +message GenerateAccessTokenResponse { + oneof result { + ErrorResponse error = 1; + AccessTokenResponseDetails accessTokenResponseDetails = 2; + } +} + +message AccessPointsList { + repeated AccessPointDetails accessPointDetails = 1; +} + +message AccessPointsResponse { + oneof result { + ErrorResponse error = 1; + AccessPointsList accessPointsResponse = 2; + } +} + +message AccessPointResponse { + oneof result { + ErrorResponse error = 1; + AccessPointDetails accessPointDetails = 2; + } +} + +message TokenResponseDetails { + string token = 1; + ResponseTokenType responseTokenType = 2; + int64 exp = 3; + string refreshToken = 4; +} + +message StatusResponse { + oneof result { + ErrorResponse error = 1; + string status = 2; + } +} +message ErrorResponse { + enum Cause { + INTERNAL_SERVER_ERROR = 0; + PHONE_NUMBER_INVALID = 1; + SID_INVALID = 2; + SID_TYPE_INVALID = 3; + EXPIRED_VERIFY_TOKEN = 4; // verified token is expired + INVALID_VERIFY_CODE = 5; + SEND_VIA_INVALID = 6; // send via parameter is invalid + MAX_VERIFY_REQUESTS_REACHED = 7; + MISSING_PROFILE_ID = 8; + INVALID_PROFILE_ID = 9; + MISSING_VERIFY_TOKEN = 10; + MISSING_VERIFY_CODE = 11; + MAX_PHONE_FAILED_ATTEMPTS_REACHED = 12; + EMAIL_INVALID = 13; // email has invalid syntax + SOCIAL_TOKEN_INVALID = 14; + MAX_DEVICE_FAILED_ATTEMPTS_REACHED = 15; //Reach the limit of the attepts which the user could make for particular device. + REFRESH_TOKEN_INVALID = 16; + ACCESS_TOKEN_INVALID_ROLE = 17; + EXPIRED_REFRESH_TOKEN = 18; + ACCESS_DISABLED = 19; + ACCESS_SUSPENDED = 20; + + //Missing verify action: adding/updating auth provider + MISSING_VERIFY_ACTION = 21; + + //Missing data for update: sidTypeToRemove, sidToRemove + MISSING_UPDATE_AUTH_PROVIDER_DATA = 22; + + //Several input fields are not valid + MULTIPLE_INVALID_PARAMETERS = 23; + + // Missing client type when loging in with Google + MISSING_CLIENT_TYPE_GOOGLE_LOGIN = 24; + + //Access token is required + MISSING_ACCESS_TOKEN = 25; + + //Account ID is a required parameter for all accesspoint rpcs + MISSING_ACCOUNT_ID = 26; + + //Invalid account ID for the rpc + INVALID_ACCOUNT_ID = 27; + + //No accesspoint(s) found for that query + ACCESSPOINTS_NOT_FOUND = 28; + + //Invalid access token + INVALID_ACCESS_TOKEN = 29; + + //Missing appClass when initiating auth/access tokens + MISSING_APP_CLASS = 30; + + //Missing instanceId when initiating auth/access tokens + MISSING_INSTANCE_ID = 31; + + //Error when Auth token is generated + AUTH_TOKEN_NOT_GENERATED = 32; + + //InstanceId is not a valid size + INVALID_INSTANCE_ID = 33; + + // error when accessing an endpoint/rpc without required permissions + ERROR_PERMISSION_DENIED = 34; + + // SidType is required for generateAccessToken + MISSING_OR_NOTSET_SIDTYPE = 35; + + // socialToken is required for generateAccessToken and sidType FACEBOOK and GOOGLEPLUS + MISSING_SOCIAL_TOKEN = 36; + + // deviceId is required for generateAccessToken + MISSING_DEVICE_ID = 37; + + // clientType is required for generateAccessToken and GOOGLEPLUS + MISSING_CLIENT_TYPE = 38; + + // Account service unavailable (down or hasn't started yet) + ACCOUNT_SERVICE_UNAVAILABLE = 39; + + // refreshToken is required for exchangeRefreshToken + MISSING_REFRESH_TOKEN = 40; + + // the operation is not supported + OPERATION_NOT_SUPPORTED = 41; + } + Cause cause = 1; + string message = 2; +} + +enum ResponseTokenType { + BEARER = 0; + CODE = 1; +} + +message SocialLoginDetails { + // picture url (for social it comes from social access token) + string pictureUrl = 1; + // first name of user + string firstName = 2; + // family name + string lastName = 3; +} + +message AccessTokenResponseDetails { + TokenResponseDetails tokenResponseDetails = 1; // details about the token + bool isPendingAccount = 2; // when true means that the account is temporary and have to be completed within 30 min. Use completePendingAccountCreation from account service. + string accountId = 3; + // SocialLoginDetails is filled with the data retrieved during social signup process + SocialLoginDetails socialLoginDetails = 4; +} + +message AccessPointDetails { + string accountId = 1; + string deviceId = 2; // Identifier of the device. + int64 creationTimestamp = 3; // Creation timestamp is set when accesspoint is created. + int64 expirationTimestamp = 4; // Keeps the expiration timestamp of the access token. +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000000000000000000000000000000000000..0eed936d6c777e1d36ae5fd3b3cb85741eae8ccc --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,88 @@ + +# SERVER PROPERTIES +server: + port: 8080 + servlet: + contextPath: /airdrop + +# CASSANDRA PROPERTIES +cassandra: + port: 9042 + keyspace: nynja + userName: null + password: null + contactPoints: 127.0.0.1 + +# KAFKA PROPERTIES +kafka: + host: localhost + port: 9092 + group: nynjaTest + topic: nynjaTest + +# OTP PROPERTIES +otp: + expireTime: 120 # Note : Expire Time is in seconds + + +# GRPC PROPERTIES +grpc: + accountService: + host: localhost + port: 6565 + authService: + host: localhost + port: 6565 + +# REST PROPERTIES +rest: + pushService: + address: "http://localhost:8082" + qoinPro: + address: "https://apis.qoinpro.com" + +# SWAGGER PROPERTIES +swagger: + apiVersion: 1.0 + licenseText: License + title: AIRDROP SERVICE + description: This Service will do verification of Nynja users who want to get coins through QoinPro + +# LOGGING PROPERTIES +logging: + level: + root: INFO + +# SPRING BOOT ACTUATOR PROPERTIES +management: + endpoint: + health: + show-details: always + metrics: + enabled: true + prometheus: + enabled: true + endpoints: + web: + exposure: + include: 'prometheus, metrics, health, info, loggers' + metrics: + export: + prometheus: + enabled: true + +#HEADER KEYS +authentication: + qoinPro: + header: + name: 'x-api-key' + value: 'NEzzAZLl0Ma1Jos1bqqfk2TFHTywQ0Qo9IAFaO0y' + + +# QOIN-PRO TOKEN AUTHENTICATION DETAILS +users: + qoinPro: + client: qoinPro + secret: qoinProSecret + userName: qoinPro + password: qoinProPassword diff --git a/src/main/resources/token/token.json b/src/main/resources/token/token.json new file mode 100644 index 0000000000000000000000000000000000000000..f23239c4806bb065e8ab3d0a83aa379693518d5e --- /dev/null +++ b/src/main/resources/token/token.json @@ -0,0 +1,4 @@ +{ + "accessToken": "eyJraWQiOiIyMDE5MDcwOCIsInR5cCI6IkpXVCIsImFsZyI6IkVTMjU2In0.eyJzdWIiOiJZVGM1TWpkalpHVXRNamhrWmkwMFpqWmlMVGxoWmpBdE4yRmlZV0l3TkdFeVpEUmwiLCJhdWQiOiJNVEl6OjEyMzoxMjMiLCJzY29wZSI6ImFjY2VzcyIsInJvbGVzIjpbIlVTRVIiXSwiaXNzIjoiaHR0cHM6Ly9hdXRoLm55bmphLmJpei8iLCJleHAiOjE1NjMyNjIyMTksImlhdCI6MTU2MjY1NzQxOX0.4gcuiVUrHNt6p4DWXN8m4HJix8heeRkHa7RVxlqONYKwjbGa_wadYi-IjW1ddXTGDi13a9ZLPCScNkbZ0Eq-Jw", + "refreshToken": "cGVtdklVZDRaZHI3Unc1QkVMenNDazl2NklYNUt4YnZRVkY1ZlMzajdPUDB3Q0cycnB1VmFXejlNUlc4QWF4S3RJRGdJS2V4a0l5MFVrSTFmL3NnTDhSWWUrWG9zMHhKZjlzOE5CS1hHUkt3SURoSm9jKzNKUXlpMGh6ZzlkUHcvQTZFQlN3WHUwcEhjSFVJZnd2RzA4dWhwZExyVHlVN0Z4L3dvL0VBUlJFc0FyQXlqbEY5SlNuOWJHeGFJQXV5dnFzemFJbUpYVEx2UTNPRWFZWHJmNDhSaDF1dnRrQ0Y1Z1pITllNUjJTbUNOZG9sa1BUczgzQ0E6YnhKSEZLaDhhY3ZHekVWVXVwU282UT09" +} \ No newline at end of file