From 5723bfe0d6dff0d21180fcbb84bbe213fed01fcd Mon Sep 17 00:00:00 2001 From: Meghajit Mazumdar Date: Sat, 4 Dec 2021 23:10:48 +0530 Subject: [PATCH 1/7] feat: add CORS support (#5) --- api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api.py b/api.py index 234a723..b1ccc02 100644 --- a/api.py +++ b/api.py @@ -1,10 +1,11 @@ import datetime from flask import Flask from flask import request +from flask_cors import CORS,cross_origin from MutualFund import MutualFund app = Flask(__name__) - +CORS(app, resources=r'/v1/*') class Demeter: From c01eb8327cc73bf3947a51af560e249761a333d3 Mon Sep 17 00:00:00 2001 From: Meghajit Mazumdar Date: Sat, 4 Dec 2021 23:18:45 +0530 Subject: [PATCH 2/7] feat: add basic UI and chart (#5) --- ui/index.html | 31 ++++++++++ ui/js/mygraph.js | 151 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 ui/index.html create mode 100644 ui/js/mygraph.js diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 0000000..2ec2c13 --- /dev/null +++ b/ui/index.html @@ -0,0 +1,31 @@ + + +

Graph

+
+ + +
+ + +
+ + +
+
+ +
+ + + + \ No newline at end of file diff --git a/ui/js/mygraph.js b/ui/js/mygraph.js new file mode 100644 index 0000000..ac684a0 --- /dev/null +++ b/ui/js/mygraph.js @@ -0,0 +1,151 @@ +let labels = []; + +const data = { + labels: labels, + datasets: [{ + label: 'My First dataset', + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(255, 0, 0)', + data: [] + }] +}; + +const config = { + type: 'line', + data: data, + options: {} +}; + +let myChart = new Chart( + document.getElementById('myChart'), + config); + +document.addEventListener("DOMContentLoaded", getFundHouses); +document.getElementById("fundHouse").addEventListener("change", getSchemes); +document.getElementById("timeFrame").addEventListener("change", getNAV); + +function getFundHouses() { + const fundHouseSelect = document.getElementById("fundHouse"); + fetch('http://localhost:5000/v1/fundhouses') + .then(response => response.json()) + .then(data => { + for(let i = 0; i < data.length; i++) { + const fundHouseName = data[i].fund_house; + const fundHouseId = data[i].fund_house_id; + const option = document.createElement("option"); + option.textContent = fundHouseName; + option.id = fundHouseId; + option.value = fundHouseId; + fundHouseSelect.appendChild(option); + }}); +} + +function createGraph(labels, data) { + const coordinates = { + labels: labels, + datasets: [{ + label: 'My First dataset', + backgroundColor: 'rgb(255, 0, 0)', + borderColor: 'rgb(255, 0, 0)', + data: data + }]}; + const config = { + type: 'line', + data: coordinates, + options: {} + }; + myChart.destroy(); + myChart = new Chart( + document.getElementById('myChart'), + config); +} + +function getSchemes(e) { + const fundHouseId = e.target.value; + const schemeSelect = document.getElementById("schemeName"); + schemeSelect.length=0; + fetch(`http://localhost:5000/v1/fundhouse/${fundHouseId}/schemes`) + .then(response => response.json()) + .then(data => { + for(let i = 0; i < data.length; i++) { + const schemeName = data[i].scheme_name; + const schemeId = data[i].scheme_id; + const option = document.createElement("option"); + option.textContent = schemeName; + option.id = schemeId; + option.value = schemeId; + schemeSelect.appendChild(option); + }}); +} + +const getThisDate = (offsetInDays) => { + let thisDate = new Date(); + thisDate.setDate(thisDate.getDate()-offsetInDays); + const d = thisDate.getDate(); + const m = thisDate.getMonth() + 1; //Month from 0 to 11 + const y = thisDate.getFullYear(); + return '' + y + '-' + (m<=9 ? '0' + m : m) + '-' + (d <= 9 ? '0' + d : d); + } + +function getNAV(e) { + const fundHouseId = document.getElementById("fundHouse").value; + const schemeId = document.getElementById("schemeName").value; + const selectedOptions = document.getElementById("timeFrame").selectedOptions; + const selectedOptionId = selectedOptions[0].id; + const endDate = getThisDate(0); + let startDate = getThisDate(1); + switch(selectedOptionId) { + case "past-week": { + startDate = getThisDate(7); + break; + } + case "past-month": { + startDate = getThisDate(31); + break; + } + case "past-six-months": { + startDate = getThisDate(186); + break; + } + case "past-one-year": { + startDate = getThisDate(365); + break; + } + case "past-three-years": { + startDate = getThisDate(1095); + break; + } + case "past-five-years": { + startDate = getThisDate(1825); + break; + } + default: startDate = getThisDate(1); + } + const data = { + startDate: startDate, + endDate: endDate, + fundHouseId: fundHouseId, + schemeId: schemeId + }; + fetch('http://localhost:5000/v1/nav', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }) + .then(response => response.json()) + .then(data => { + const xCoords = []; + const yCoords = []; + for(let i = 0; i < data.length; i++) { + xCoords.push(data[i].date); + yCoords.push(data[i].nav); + } + createGraph(xCoords, yCoords); + }) + .catch(error => console.error(error)); +} + + + From c3b12b4c440c7ad61bf8d42a4c2e69054208ed50 Mon Sep 17 00:00:00 2001 From: Meghajit Mazumdar Date: Sat, 4 Dec 2021 23:22:36 +0530 Subject: [PATCH 3/7] feat: add dependency in requirements.txt (#5) --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b93c56d..d1401d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ requests-html==0.10.0 -flask==2.0.1 \ No newline at end of file +flask==2.0.1 +flask_cors==3.0.10 \ No newline at end of file From 94954eb84930156ea166d2e285a306977e00b52a Mon Sep 17 00:00:00 2001 From: Meghajit Mazumdar Date: Sun, 5 Dec 2021 03:16:54 +0530 Subject: [PATCH 4/7] feat: add web server in dockerfile - upgrade to Ubuntu 18.04 - remove deprecated ppa fpr python 3.7 - add apache webserver to serve the static HTML and JS files --- Dockerfile | 14 +++++++------- start.sh | 7 +++++++ 2 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 start.sh diff --git a/Dockerfile b/Dockerfile index 71a8fda..b8f1bdb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,16 @@ -FROM ubuntu:16.04 +FROM ubuntu:18.04 +FROM httpd:2.4 RUN groupadd container_users && useradd -ms /bin/bash -g container_users belmont RUN apt-get update && apt-get -y install software-properties-common RUN apt-get -y install build-essential -RUN add-apt-repository ppa:deadsnakes/ppa -RUN apt-get update && apt-get -y install python3.7 -RUN rm /usr/bin/python3 -RUN ln -s /usr/bin/python3.7 /usr/bin/python3 RUN apt-get -y install python3-pip RUN python3 -m pip install --upgrade pip RUN apt-get -y install curl +# giving access of apache logs directory to belmont user and the group +RUN chown belmont:container_users /usr/local/apache2/logs + USER belmont ENV DEMETER_DIR /home/belmont/demeter/ @@ -22,7 +22,7 @@ RUN mkdir -p $DEMETER_DIR ADD . $DEMETER_DIR WORKDIR $DEMETER_DIR +COPY ./ui/ /usr/local/apache2/htdocs/ RUN python3 -m pip install -r requirements.txt - -CMD flask run --port=8080 --host=0.0.0.0 \ No newline at end of file +CMD bash start.sh \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..3348f5a --- /dev/null +++ b/start.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +flask run --port=5000 --host=0.0.0.0 & +P1=$! +httpd-foreground & +P2=$! +wait $P1 $P2 \ No newline at end of file From fae32f4beb44d3d715bc9f4086e5800f092b8807 Mon Sep 17 00:00:00 2001 From: Meghajit Mazumdar Date: Sun, 5 Dec 2021 03:30:24 +0530 Subject: [PATCH 5/7] doc: add documentation for UI --- README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index def4404..3623bb7 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,24 @@ This project requires Python 3.7+ and pip ### Alternatively, if you want to run it in docker #### 1.Build the image -`docker build -t demeter:0.0.1 .` +`docker build -t demeter:0.0.2 .` #### 2. Run the container -`docker run -p 8080:8080 -d demeter:0.0.1` +`docker run -p 8080:8080 -d demeter:0.0.2` + + +### Frontend +Demeter also offers a minimal UI to see the NAV of a scheme. If you want to run it locally, you can start the API +server as mentioned above and then expose the UI by serving the `ui` directory static files from any web server such as +Apache Web Server or Nginx Plus. Alternatively, if you want to serve it via Docker, you can follow the steps: + +#### 1.Build the image +`docker build -t demeter:0.0.2 .` + +#### 2. Run the container +`docker run -d --name demeter -p 8080:80 -p 5000:5000 demeter:0.0.2` + +Demeter will start an Apache Web Server instance at port 8080 and serve the UI. ## REST Endpoints From 1b273a9a54b1cf12c3598122c3e9bee5ed5a7a10 Mon Sep 17 00:00:00 2001 From: Pinak Mazumdar Date: Fri, 7 Jan 2022 21:18:34 +0530 Subject: [PATCH 6/7] Add sendmail function --- Dockerfile | 18 +++++++--- MutualFund.py | 1 - api.py | 5 ++- files/contactslist.txt | 3 ++ files/mutualfund.jpg | Bin 0 -> 23729 bytes getNav.sh | 70 +++++++++++++++++++++++++++++++++++++++ send_email.py | 73 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 files/contactslist.txt create mode 100644 files/mutualfund.jpg create mode 100644 getNav.sh create mode 100644 send_email.py diff --git a/Dockerfile b/Dockerfile index b8f1bdb..10eb705 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,38 @@ FROM ubuntu:18.04 FROM httpd:2.4 +FROM python:3 RUN groupadd container_users && useradd -ms /bin/bash -g container_users belmont +RUN echo "Acquire::Check-Valid-Until \"false\";\nAcquire::Check-Date \"false\";" | cat > /etc/apt/apt.conf.d/10no--check-valid-until RUN apt-get update && apt-get -y install software-properties-common RUN apt-get -y install build-essential RUN apt-get -y install python3-pip RUN python3 -m pip install --upgrade pip RUN apt-get -y install curl +RUN apt-get -y install jq + -# giving access of apache logs directory to belmont user and the group -RUN chown belmont:container_users /usr/local/apache2/logs USER belmont -ENV DEMETER_DIR /home/belmont/demeter/ +ENV DEMETER_DIR /home/belmont/demeter ENV PATH /home/belmont/.local/bin:$PATH ENV FLASK_APP="api.py" ENV FLASK_ENV="production" RUN mkdir -p $DEMETER_DIR +RUN mkdir -p $DEMETER_DIR/files + ADD . $DEMETER_DIR +USER root +RUN chown -R belmont:container_users $DEMETER_DIR/files/ + +USER belmont +COPY ./files/ $DEMETER_DIR/files/ + WORKDIR $DEMETER_DIR COPY ./ui/ /usr/local/apache2/htdocs/ RUN python3 -m pip install -r requirements.txt -CMD bash start.sh \ No newline at end of file +CMD ["bash", "getNav.sh"] diff --git a/MutualFund.py b/MutualFund.py index 867bc10..59766c8 100644 --- a/MutualFund.py +++ b/MutualFund.py @@ -2,7 +2,6 @@ import json import datetime - class MutualFund: def __init__(self): diff --git a/api.py b/api.py index b1ccc02..9a8a054 100644 --- a/api.py +++ b/api.py @@ -23,7 +23,10 @@ def get_schemes(fund_house_id: int): @app.route('/v1/nav', methods=["POST"]) def get_historic_nav(): + req_body = request.json + + start_date_splitted = req_body["startDate"].split('-') start_date = datetime.datetime(int(start_date_splitted[0]), int(start_date_splitted[1]), int(start_date_splitted[2])) @@ -32,4 +35,4 @@ def get_historic_nav(): response = app.make_response(MutualFund.get_historic_nav( req_body["fundHouseId"], req_body["schemeId"], start_date, end_date)) response.content_type = "application/json" - return response + return response \ No newline at end of file diff --git a/files/contactslist.txt b/files/contactslist.txt new file mode 100644 index 0000000..529ba34 --- /dev/null +++ b/files/contactslist.txt @@ -0,0 +1,3 @@ +Pinak mazumdar.pinak@gmail.com +Meghajit megh319@gmail.com +Debosree debosree26@gmail.com \ No newline at end of file diff --git a/files/mutualfund.jpg b/files/mutualfund.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f95baa204d6513a598da4c9cc88bda559c139983 GIT binary patch literal 23729 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 Q(jv^oqZU9Ncr*9E0It*|)&Kwi literal 0 HcmV?d00001 diff --git a/getNav.sh b/getNav.sh new file mode 100644 index 0000000..d1143de --- /dev/null +++ b/getNav.sh @@ -0,0 +1,70 @@ +#!/bin/bash +########################################################################################### +#Purpose: Invoke historical NAV for a particular MF scheme everyday using a crontab job # +#Name: getNav.sh # +#Author: Pinak Mazumdar # +########################################################################################### +getFundHouseID() + +{ +URL="localhost:8080/v1/fundhouses" +FUND_HOUSES=`curl --location --request GET "$URL"` +FUND_HOUSE_ID=`echo $FUND_HOUSES | jq '.[] | select(.fund_house=="Mirae Asset Mutual Fund")' | jq '.fund_house_id'` +echo $FUND_HOUSE_ID +} + +getSchemes() + +{ + id=${FUND_HOUSE_ID} + URL="localhost:8080/v1/fundhouse/${id}/schemes" + SCHEMES=`curl --location --request GET "$URL"` + SCHEME_ID=`echo $SCHEMES | jq '.[] | select(.scheme_name=="Mirae Asset Tax Saver Fund-Direct Plan -Growth")' | jq '.scheme_id'` + echo $SCHEME_ID +} + + +getNAV() +{ + +enddate=`date -d '1 day ago' +'%Y-%m-%d'` +startdate=`date -d '1 day ago' +'%Y-%m-%d'` +fundid="$FUND_HOUSE_ID" +schemeid="$SCHEME_ID" +CURLURL="localhost:8080/v1/nav" +CURLDATA="{\ +"\"startDate"\": "\"${startdate}"\", \ +"\"endDate"\": "\"${enddate}"\", \ +"\"fundHouseId"\": "${fundid}", \ +"\"schemeId"\": "${schemeid}"\ +}" + +RESPONSE=`curl --location --request POST "$CURLURL" --header 'Content-Type: application/json' --data-raw "$CURLDATA"` +#echo "Latest NAV: ${RESPONSE}" +} + +getParsedOutput(){ +date=`echo $RESPONSE | jq '.[].date'` +nav=`echo $RESPONSE | jq '.[].nav'` +#echo $date +#echo $nav +msg="Dear \${PERSON_NAME} \n\nThank you for showing interest in receiving daily NAV details for your selected mutual fund scheme. \n\nThe NAV(Net Asset Value) for the mutual fund scheme you selected is ${nav} as on ${date}. \n\nIf you want to unsubscribe from this service.Please send SMS STOP NAV to 9742061425.\n\nThanks,\nAnna-konda" +echo -e "$msg" >files/getNAV.out +} + + +##Main function +startProcess() +{ +FLASK_APP=api.py flask run --port=8080 +} + +startProcess & +sleep 5 +getFundHouseID +getSchemes $FUND_HOUSE_ID +getNAV $FUND_HOUSE_ID $SCHEME_ID +getParsedOutput $RESPONSE +python3 send_email.py +echo "-----End of Script------" + diff --git a/send_email.py b/send_email.py new file mode 100644 index 0000000..5b39d68 --- /dev/null +++ b/send_email.py @@ -0,0 +1,73 @@ +# import necessary packages +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from email.mime.base import MIMEBase +from email import encoders +import time +import smtplib +from string import Template + +def get_contacts(filename): + names = [] + emails = [] + with open(filename, mode='r', encoding='utf-8') as contacts_file: + for a_contact in contacts_file: + names.append(a_contact.split()[0]) + emails.append(a_contact.split()[1]) + return names, emails + +def read_template(filename): + with open(filename, 'r', encoding='utf-8') as template_file: + template_file_content = template_file.read() + return Template(template_file_content) + +def sendemail(): + + MY_ADDRESS = "put real email id here" + PASSWORD = "put real password here" + files = ['files/mutualfund'] + + # set up the SMTP server + s = smtplib.SMTP(host='smtp.gmail.com', port=587) + s.starttls() + s.login(MY_ADDRESS, PASSWORD) + + time.sleep(60) #Sleep for 5 minutes + + names, emails = get_contacts('files/contactslist.txt') # read contacts + message_template = read_template('files/getNAV.out') + filename = "mutualfund.jpg" + # For each contact, send the email: + for name, email in zip(names, emails): + msg = MIMEMultipart() # create a message + print(name, email) + # add in the actual person name to the message template + message = message_template.substitute(PERSON_NAME=name.title()) + + # setup the parameters of the message + msg['From']=MY_ADDRESS + msg['To']=email + msg['Subject']="Daily Net Asset Value Mailer" #Change to Body ltr + + # add in the message body + msg.attach(MIMEText(message, 'plain')) + # msg.attach(files) + attachment = open("files/mutualfund.jpg" , "rb") + p = MIMEBase('application', 'octet-stream') + # To change the payload into encoded form + p.set_payload((attachment).read()) + # encode into base64 + encoders.encode_base64(p) + + p.add_header('Content-Disposition', "attachment; filename= %s" % filename) + + # attach the instance 'p' to instance 'msg' + msg.attach(p) + + # send the message via the server set up earlier. + s.send_message(msg) + + del msg + +if __name__ == '__main__': + sendemail() From 3f03c41462c944eb87d7920dfc4ecea09c2b0a5d Mon Sep 17 00:00:00 2001 From: pinakmazumdar Date: Sat, 15 Jan 2022 22:11:36 +0530 Subject: [PATCH 7/7] Removed some whitespaces Remove layer for installing python3 Removed email addresses --- Dockerfile | 3 --- files/contactslist.txt | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 10eb705..8066399 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,5 @@ FROM ubuntu:18.04 FROM httpd:2.4 -FROM python:3 RUN groupadd container_users && useradd -ms /bin/bash -g container_users belmont RUN echo "Acquire::Check-Valid-Until \"false\";\nAcquire::Check-Date \"false\";" | cat > /etc/apt/apt.conf.d/10no--check-valid-until @@ -11,8 +10,6 @@ RUN python3 -m pip install --upgrade pip RUN apt-get -y install curl RUN apt-get -y install jq - - USER belmont ENV DEMETER_DIR /home/belmont/demeter diff --git a/files/contactslist.txt b/files/contactslist.txt index 529ba34..f8e98b2 100644 --- a/files/contactslist.txt +++ b/files/contactslist.txt @@ -1,3 +1 @@ -Pinak mazumdar.pinak@gmail.com -Meghajit megh319@gmail.com -Debosree debosree26@gmail.com \ No newline at end of file +Name email-id