From 108378a31e77db490aafc46ba643cbfa51ba2ad0 Mon Sep 17 00:00:00 2001 From: "CHIEFSOFT\\ameye" Date: Sat, 3 May 2025 22:16:10 -0400 Subject: [PATCH] first commit --- .env | 16 + .env.dev | 16 + .env.dev-sample | 17 + .env.prod-sample | 7 + .env.prod.db-sample | 3 + .gitignore | 8 + .idea/.gitignore | 8 + .idea/MyFit-CoreBackEnd.iml | 12 + .idea/inspectionProfiles/Project_Default.xml | 6 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + LICENSE | 21 + README.md | 47 + docker-compose.prod.yml | 20 + docker-compose.yml | 13 + env.dev | 7 + services/nginx/Dockerfile | 4 + services/nginx/nginx.conf | 24 + services/web/Dockerfile | 23 + services/web/Dockerfile.prod | 69 ++ services/web/email/verify.html | 2 + services/web/entrypoint.prod.sh | 14 + services/web/entrypoint.sh | 16 + services/web/manage.py | 23 + services/web/project/__init__.py | 1054 +++++++++++++++++ services/web/project/config.py | 11 + services/web/project/media/.gitkeep | 0 services/web/project/models/members.py | 95 ++ services/web/project/static/hello.txt | 1 + services/web/project/validate/validate.py | 45 + services/web/requirements.txt | 15 + 33 files changed, 1624 insertions(+) create mode 100644 .env create mode 100644 .env.dev create mode 100644 .env.dev-sample create mode 100644 .env.prod-sample create mode 100644 .env.prod.db-sample create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/MyFit-CoreBackEnd.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docker-compose.prod.yml create mode 100644 docker-compose.yml create mode 100644 env.dev create mode 100644 services/nginx/Dockerfile create mode 100644 services/nginx/nginx.conf create mode 100644 services/web/Dockerfile create mode 100644 services/web/Dockerfile.prod create mode 100644 services/web/email/verify.html create mode 100644 services/web/entrypoint.prod.sh create mode 100644 services/web/entrypoint.sh create mode 100644 services/web/manage.py create mode 100644 services/web/project/__init__.py create mode 100644 services/web/project/config.py create mode 100644 services/web/project/media/.gitkeep create mode 100644 services/web/project/models/members.py create mode 100644 services/web/project/static/hello.txt create mode 100644 services/web/project/validate/validate.py create mode 100644 services/web/requirements.txt diff --git a/.env b/.env new file mode 100644 index 0000000..aa1cb09 --- /dev/null +++ b/.env @@ -0,0 +1,16 @@ +APP_PORT=14500 +FLASK_APP=project/__init__.py +FLASK_DEBUG=1 +SOCKET_URL=https://dev-socket.mermsemr.com +PANEL_URL=https://dev-panel.mermsemr.com +DATABASE_URL=postgresql://merms_panel:merms_panel@10.20.30.60:5432/merms_panel +SQL_HOST=10.20.30.60 +SQL_PORT=5432 +DATABASE=postgres +APP_FOLDER=/usr/src/app +INITIAL_PRODUCT_URL=devprov.mermsemr.com +MAIL_SERVER=smtp.gmail.com +MAIL_PORT= 465 +MAIL_USERNAME=message@chiefsoft.com +MAIL_PASSWORD=may12002! +JWT_SECRET=dce6bd64f7d7101de4fed7cfc185a12851611a79bd60bbfdcc5b414b85f1fdb75e0905691c2a77ce94a7351b261fab4e183e17731ed40089f68f7290a793119f285d8ec7902d248ce15e8b1d4996ebacf5e7bcb06a38ac7ce0736f17d5c2895a499661d27095ac20aa174f9af2fba9a849dd2e6fd0aad8aa7e1ecc030c11eb8dc8dcb71d32233de3530d04f85918b9582f8b02587a7350aa34232825d4831707c7c5775026f3fdd92c5df555e6ff8b785525922709830206bbd49c371fb6e16bcab01ffccaf904108bb9789c578bce6afbb33bc77960051e680b2428d1f026473e71ef0f9997b2a6dc496e1c40509a1d289e3ff8b384c2d49e1d7719f9f4aaa1 \ No newline at end of file diff --git a/.env.dev b/.env.dev new file mode 100644 index 0000000..aa1cb09 --- /dev/null +++ b/.env.dev @@ -0,0 +1,16 @@ +APP_PORT=14500 +FLASK_APP=project/__init__.py +FLASK_DEBUG=1 +SOCKET_URL=https://dev-socket.mermsemr.com +PANEL_URL=https://dev-panel.mermsemr.com +DATABASE_URL=postgresql://merms_panel:merms_panel@10.20.30.60:5432/merms_panel +SQL_HOST=10.20.30.60 +SQL_PORT=5432 +DATABASE=postgres +APP_FOLDER=/usr/src/app +INITIAL_PRODUCT_URL=devprov.mermsemr.com +MAIL_SERVER=smtp.gmail.com +MAIL_PORT= 465 +MAIL_USERNAME=message@chiefsoft.com +MAIL_PASSWORD=may12002! +JWT_SECRET=dce6bd64f7d7101de4fed7cfc185a12851611a79bd60bbfdcc5b414b85f1fdb75e0905691c2a77ce94a7351b261fab4e183e17731ed40089f68f7290a793119f285d8ec7902d248ce15e8b1d4996ebacf5e7bcb06a38ac7ce0736f17d5c2895a499661d27095ac20aa174f9af2fba9a849dd2e6fd0aad8aa7e1ecc030c11eb8dc8dcb71d32233de3530d04f85918b9582f8b02587a7350aa34232825d4831707c7c5775026f3fdd92c5df555e6ff8b785525922709830206bbd49c371fb6e16bcab01ffccaf904108bb9789c578bce6afbb33bc77960051e680b2428d1f026473e71ef0f9997b2a6dc496e1c40509a1d289e3ff8b384c2d49e1d7719f9f4aaa1 \ No newline at end of file diff --git a/.env.dev-sample b/.env.dev-sample new file mode 100644 index 0000000..c5ffe0a --- /dev/null +++ b/.env.dev-sample @@ -0,0 +1,17 @@ +FLASK_APP=project/__init__.py +FLASK_DEBUG=1 +DATABASE_URL=postgresql://hello_flask:hello_flask@db:5432/hello_flask_dev +SQL_HOST=db +SQL_PORT=5432 +DATABASE=postgres +APP_FOLDER=/usr/src/app + + +FLASK_APP=project/__init__.py +FLASK_DEBUG=1 +DATABASE_URL=postgresql://hello_flask:hello_flask@db:5432/hello_flask_dev +SQL_HOST=db +SQL_PORT=5432 +DATABASE=postgres +APP_FOLDER=/usr/src/app + diff --git a/.env.prod-sample b/.env.prod-sample new file mode 100644 index 0000000..451cf9e --- /dev/null +++ b/.env.prod-sample @@ -0,0 +1,7 @@ +FLASK_APP=project/__init__.py +FLASK_DEBUG=0 +DATABASE_URL=postgresql://hello_flask:hello_flask@db:5432/hello_flask_prod +SQL_HOST=db +SQL_PORT=5432 +DATABASE=postgres +APP_FOLDER=/home/app/web diff --git a/.env.prod.db-sample b/.env.prod.db-sample new file mode 100644 index 0000000..3acb993 --- /dev/null +++ b/.env.prod.db-sample @@ -0,0 +1,3 @@ +POSTGRES_USER=hello_flask +POSTGRES_PASSWORD=hello_flask +POSTGRES_DB=hello_flask_prod diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8990cc1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.pyc +__pycache +.DS_Store +#.env.dev +#.env.prod +.env.prod.db +postgres_data +postgres_data* \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/MyFit-CoreBackEnd.iml b/.idea/MyFit-CoreBackEnd.iml new file mode 100644 index 0000000..b5ad51a --- /dev/null +++ b/.idea/MyFit-CoreBackEnd.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a6218fe --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..7dc36cb --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..29812bb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 TestDriven.io + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c48883 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Dockerizing Flask with Postgres, Gunicorn, and Nginx + +## Want to learn how to build this? + +Check out the [tutorial](https://testdriven.io/blog/dockerizing-flask-with-postgres-gunicorn-and-nginx). + +## Want to use this project? + +### Development + +Uses the default Flask development server. + +1. Rename *.env.dev-sample* to *.env.dev*. +1. Update the environment variables in the *docker-compose.yml* and *.env.dev* files. + - (M1 chip only) Remove `-slim-buster` from the Python dependency in `services/web/Dockerfile` to suppress an issue with installing psycopg2 +1. Build the images and run the containers: + + ```sh + $ docker-compose up -d --build + ``` + + Test it out at [http://localhost:5001](http://localhost:5001). The "web" folder is mounted into the container and your code changes apply automatically. + +### Production + +Uses gunicorn + nginx. + +1. Rename *.env.prod-sample* to *.env.prod* and *.env.prod.db-sample* to *.env.prod.db*. Update the environment variables. +1. Build the images and run the containers: + + ```sh + $ docker-compose -f docker-compose.prod.yml up -d --build + ``` + + Test it out at [http://localhost:1337](http://localhost:1337). No mounted folders. To apply changes, the image must be re-built. + + + +git init + +git add . + +git commit -m "first commit" + +git remote add origin https://gitlab.chiefsoft.net/MERMS/MermsCoreBackendFlask.git + +git push -u origin master diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..29ceb2e --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,20 @@ +version: '3.8' + +services: + myfit-corebackend: + build: ./services/web + command: python manage.py run -h 0.0.0.0 + volumes: + - ./services/web/:/usr/src/app/ + ports: + - "${APP_PORT:-14500}:5000" + env_file: + - ./.env.dev + restart: always + +#networks: +# default: +## driver: bridge +# ipam: +# config: +# - subnet: 10.10.33.0/24 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..375012f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.8' + +services: + myfit-corebackend: + build: ./services/web + command: python manage.py run -h 0.0.0.0 + volumes: + - ./services/web/:/usr/src/app/ + ports: + - "${APP_PORT:-14500}:5000" + env_file: + - ./.env.dev + restart: always diff --git a/env.dev b/env.dev new file mode 100644 index 0000000..789ff86 --- /dev/null +++ b/env.dev @@ -0,0 +1,7 @@ +FLASK_APP=project/__init__.py +FLASK_DEBUG=1 +DATABASE_URL=postgresql://hello_flask:hello_flask@db:5432/hello_flask_dev +SQL_HOST=db +SQL_PORT=5432 +DATABASE=postgres +APP_FOLDER=/usr/src/app \ No newline at end of file diff --git a/services/nginx/Dockerfile b/services/nginx/Dockerfile new file mode 100644 index 0000000..8328a0e --- /dev/null +++ b/services/nginx/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx:1.25 + +RUN rm /etc/nginx/conf.d/default.conf +COPY nginx.conf /etc/nginx/conf.d diff --git a/services/nginx/nginx.conf b/services/nginx/nginx.conf new file mode 100644 index 0000000..02689b8 --- /dev/null +++ b/services/nginx/nginx.conf @@ -0,0 +1,24 @@ +upstream hello_flask { + server web:5000; +} + +server { + + listen 80; + + location / { + proxy_pass http://hello_flask; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_redirect off; + } + + location /static/ { + alias /home/app/web/project/static/; + } + + location /media/ { + alias /home/app/web/project/media/; + } + +} diff --git a/services/web/Dockerfile b/services/web/Dockerfile new file mode 100644 index 0000000..54a20c6 --- /dev/null +++ b/services/web/Dockerfile @@ -0,0 +1,23 @@ +# pull official base image +FROM python:3.11.3-slim-buster + +# set work directory +WORKDIR /usr/src/app + +# set environment variables +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +# install system dependencies +RUN apt-get update && apt-get install -y netcat + +# install dependencies +RUN pip install --upgrade pip +COPY ./requirements.txt /usr/src/app/requirements.txt +RUN pip install -r requirements.txt + +# copy project +COPY . /usr/src/app/ + +# run entrypoint.sh +ENTRYPOINT ["/usr/src/app/entrypoint.sh"] diff --git a/services/web/Dockerfile.prod b/services/web/Dockerfile.prod new file mode 100644 index 0000000..4974c67 --- /dev/null +++ b/services/web/Dockerfile.prod @@ -0,0 +1,69 @@ +########### +# BUILDER # +########### + +# pull official base image +FROM python:3.11.3-slim-buster as builder + +# set work directory +WORKDIR /usr/src/app + +# set environment variables +ENV PYTHONDONTWRITEBYTECODE 1 +ENV PYTHONUNBUFFERED 1 + +# install system dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends gcc + +# lint +RUN pip install --upgrade pip +RUN pip install flake8==6.0.0 +COPY . /usr/src/app/ +RUN flake8 --ignore=E501,F401 . + +# install python dependencies +COPY ./requirements.txt . +RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt + + +######### +# FINAL # +######### + +# pull official base image +FROM python:3.11.3-slim-buster + +# create directory for the app user +RUN mkdir -p /home/app + +# create the app user +RUN addgroup --system app && adduser --system --group app + +# create the appropriate directories +ENV HOME=/home/app +ENV APP_HOME=/home/app/web +RUN mkdir $APP_HOME +WORKDIR $APP_HOME + +# install dependencies +RUN apt-get update && apt-get install -y --no-install-recommends netcat +COPY --from=builder /usr/src/app/wheels /wheels +COPY --from=builder /usr/src/app/requirements.txt . +RUN pip install --upgrade pip +RUN pip install --no-cache /wheels/* + +# copy entrypoint-prod.sh +COPY ./entrypoint.prod.sh $APP_HOME + +# copy project +COPY . $APP_HOME + +# chown all the files to the app user +RUN chown -R app:app $APP_HOME + +# change to the app user +USER app + +# run entrypoint.prod.sh +ENTRYPOINT ["/home/app/web/entrypoint.prod.sh"] diff --git a/services/web/email/verify.html b/services/web/email/verify.html new file mode 100644 index 0000000..2139f2f --- /dev/null +++ b/services/web/email/verify.html @@ -0,0 +1,2 @@ +Hello MERMS message sent for account verification +https://works.mermsemr.com/csignup/JWT-djhgdhjgdhdggggd diff --git a/services/web/entrypoint.prod.sh b/services/web/entrypoint.prod.sh new file mode 100644 index 0000000..37fa201 --- /dev/null +++ b/services/web/entrypoint.prod.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +if [ "$DATABASE" = "postgres" ] +then + echo "Waiting for postgres..." + + while ! nc -z $SQL_HOST $SQL_PORT; do + sleep 0.1 + done + + echo "PostgreSQL started" +fi + +exec "$@" diff --git a/services/web/entrypoint.sh b/services/web/entrypoint.sh new file mode 100644 index 0000000..3069d16 --- /dev/null +++ b/services/web/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +if [ "$DATABASE" = "postgres" ] +then + echo "Waiting for postgres..." + + while ! nc -z $SQL_HOST $SQL_PORT; do + sleep 0.1 + done + + echo "PostgreSQL started" +fi + +python manage.py create_db + +exec "$@" diff --git a/services/web/manage.py b/services/web/manage.py new file mode 100644 index 0000000..d9c3f04 --- /dev/null +++ b/services/web/manage.py @@ -0,0 +1,23 @@ +from flask.cli import FlaskGroup + +from project import app, db, User + + +cli = FlaskGroup(app) + + +@cli.command("create_db") +def create_db(): + db.drop_all() + db.create_all() + db.session.commit() + + +@cli.command("seed_db") +def seed_db(): + db.session.add(User(email="michael@mherman.org")) + db.session.commit() + + +if __name__ == "__main__": + cli() diff --git a/services/web/project/__init__.py b/services/web/project/__init__.py new file mode 100644 index 0000000..b803779 --- /dev/null +++ b/services/web/project/__init__.py @@ -0,0 +1,1054 @@ +import os +import psycopg2 +from dotenv import load_dotenv +from functools import wraps +import datetime +import jwt +import random +import json +import psycopg2.extras +import pandas as pd + +import project.validate.validate as validate +#import project.models.members as Members +from project.models.members import Members +from flask_cors import CORS +from flasgger import Swagger, swag_from +from flask_mail import Mail, Message +from flask import ( + Flask, + jsonify, + send_from_directory, + request, +) +from flask_sqlalchemy import SQLAlchemy + +from sqlalchemy import create_engine +# import socket +#import SQLAlchemy +#from werkzeug.utils import secure_filename + +load_dotenv() + +app = Flask(__name__) +CORS(app) +app.config.from_object("project.config.Config") +db = SQLAlchemy(app) +#jwt_secret = os.getenv("JWT_SECRET") +app.config['SECRET_KEY'] = os.getenv("JWT_SECRET") +panel_url = os.getenv("PANEL_URL") + +mail = Mail(app) # instantiate the mail class + +# configuration of mail +app.config['MAIL_SERVER']=os.getenv("MAIL_SERVER") # 'smtp.gmail.com' +app.config['MAIL_PORT'] = os.getenv("MAIL_PORT") # 465 +#app.config['MAIL_PORT'] = 587 +app.config['MAIL_USERNAME'] = os.getenv("MAIL_USERNAME") # 'message@chiefsoft.com' +app.config['MAIL_PASSWORD'] = os.getenv("MAIL_PASSWORD") # 'may12002!' +app.config['MAIL_USE_TLS'] = False +app.config['MAIL_USE_SSL'] = True +mail = Mail(app) + + + +template = { + "swagger": "2.0", + "info": { + "title": "MERMS Core API", + "description": "This API was developed using Python Flask, which provides an interface for core MERMS endpoints.", + "version": "1.0" + } + } + +app.config['SWAGGER'] = { + 'title': 'MERMS API', + 'uiversion': 2, + 'template': './resources/flasgger/swagger_ui.html' + } + +Swagger(app, template=template) +@swag_from('../../docs/consume.yml') + +def token_required(f): + @wraps(f) + def decorated(*args, **kwargs): + #token = request.args.get('token') + token = request.headers["Authorization"].split(" ")[1] +# print(token) + + if not token: + return jsonify({'message': 'Error - missing token'}), 403 + try: + data= jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"]) + except: + return jsonify({'message': 'Token is invalid'}),403 + + return f(data, *args, **kwargs) + return decorated + +class User(db.Model): + __tablename__ = "users" + + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(128), unique=True, nullable=False) + active = db.Column(db.Boolean(), default=True, nullable=False) + + def __init__(self, email): + self.email = email + +initial_product_url = os.getenv("INITIAL_PRODUCT_URL") +print(initial_product_url) + +dataUrl = os.getenv("DATABASE_URL") +connection = psycopg2.connect(dataUrl) +#engine = SQLAlchemy.create_engine(dataUrl) +engine = create_engine(dataUrl) + +@app.route("/") +def hello_world(): + email="ameye+" + str( random.randint(1000, 99999)) + "@chiefsoft.com" + firstname="First" + str(random.randint(10, 100)) + lastname="Last" + str(random.randint(10, 100)); + + SELECT_INSERT = "SELECT uid, firstname , lastname , email FROM members_pending WHERE status = 0 AND email='" + email + "'" + print(SELECT_INSERT) + ADJUST_PREVIOUS = "UPDATE members_pending SET status = 3 WHERE status = 0 AND email='" + email + "'" + val_update = (email) + val_insert = (email, firstname, lastname) + INSERT_MEMBERS = "INSERT INTO members_pending(email,firstname,lastname) VALUES(%s,%s,%s)" + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(ADJUST_PREVIOUS) + cursor.execute(INSERT_MEMBERS, val_insert) + # connection.commit() +# last_row_id=cursor.lastrowid +# print('After Insert ::: ') +# #connection.insert_id() +# print(last_row_id) + + SELECT_INSERT = "SELECT uid, firstname , lastname , email FROM members_pending WHERE status = 0 AND email='" + email + "'" + print(SELECT_INSERT) + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(SELECT_INSERT) + select_pendingRes = cursor.fetchall() + + print(select_pendingRes) + select_pending = json.dumps( [dict(ix) for ix in select_pendingRes] ) + print(select_pending) + array3 = json.loads(select_pending) + + return jsonify(action_data="sent", select_pending=array3) + +@app.route("/test/contacts") +def test_caontacts(): + + dList = [] + sample_range = random.randint(20, 60) + for x in range(sample_range): + calDate = datetime.datetime.utcnow() + datetime.timedelta(minutes=180 * random.randint(1, 20)) + new_l = { + "uid":"425611f2-c692-4404-b93d-76ca7a5ce7"+str(x), + "title": "Calendar Random Item on " + str(x) , + "added": calDate, + "sender": "Firstname Lastname" + str(random.randint(1, 4)), + "message" : dummy_message() + } + dList.append(new_l) + + calendar_data = { + "last_update": datetime.datetime.utcnow(), + "category": [ + {"cid": "1", "description": "category 01" }, + {"cid": "2", "description": "category 02" }, + {"cid": "3", "description": "category 03" }, + {"cid": "4", "description": "category 04" }, + ], + "contacts" : dList + } + return jsonify(calendar_data=calendar_data) + +def dummy_message(): + dmm = "Dmummy Message" + str(random.randint(100, 400)) + mss = f""" + {dmm}I truly believe Augustine’s words are true and if you look at history you know it is true. There are many people in the world with amazing talents who realize only a small percentage of their potential. We all know people who live this truth. + + We also know those epic stories, those modern-day legends surrounding the early failures of such supremely successful folks as Michael Jordan and Bill Gates. We can look a bit further back in time to Albert Einstein or even further back to Abraham Lincoln. What made each of these people so successful? Motivation. + + We know this in our gut, but what can we do about it? How can we motivate ourselves? One of the most difficult aspects of achieving success is staying motivated over the long haul. + """ + return mss + +@app.route("/test/calendar") +def test_calen(): + SELECT_LAST_ENTRY = f"SELECT id, uid AS product_uid FROM members_products WHERE internal_url='23607.devprov.mermsemr.com'" + print(SELECT_LAST_ENTRY) + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(SELECT_LAST_ENTRY) + select_pendingRes = cursor.fetchall() + + print(select_pendingRes) + myproduct_data = json.dumps( [dict(ix) for ix in select_pendingRes] ) + print(myproduct_data) + print(select_pendingRes[0][0]) + print(select_pendingRes[0][1]) + + dList = [] + sample_range = random.randint(20, 60) + for x in range(sample_range): + calDate = datetime.datetime.utcnow() + datetime.timedelta(minutes=180 * random.randint(1, 20)) + new_l = {"uid":"425611f2-c692-4404-b93d-76ca7a5ce7"+str(x), "description": "Calendar Random Item on " + str(x) , "added": calDate, "category":random.randint(1, 4) } + dList.append(new_l) + + calendar_data = { + "last_update": datetime.datetime.utcnow(), + "category": [ + {"cid": "1", "description": "category 01" }, + {"cid": "2", "description": "category 02" }, + {"cid": "3", "description": "category 03" }, + {"cid": "4", "description": "category 04" }, + ], + "list" : dList + } + return jsonify(calendar_data=calendar_data) + +@app.route("/test/products") +def test_product(): + myproudct = myproduct_detail(5 , 'A000001') + print(myproudct) + + products_data = { + "last_update": datetime.datetime.utcnow(), + "products": [ + {"uid":"A000001","icon": "icon_product", "description": "Professional Website" , "status": 'Activate now'}, + {"uid":"A000002","icon": "icon_product", "description": "Professional Blog" , "status": 'Activate now'}, + {"uid":"A000003","icon": "icon_product", "description": "Business Website" , "status": 'Activate now'}, + {"uid":"A000004","icon": "icon_product", "description": "Business Blog Site" , "status": 'Activate now'}, + {"uid":"A000005","icon": "icon_product", "description": "OpenEmr" , "status": 'Activate now'}, + {"uid":"A000005","icon": "icon_product", "description": "Dummy Dummy" , "status": 'Activate now'}, + ] + } + print(products_data) + PRODUCT_LIST ="SELECT id,uid AS product_uid,product_id,name,description,status,banner, 'Activate Now' AS status_text FROM products ORDER BY id ASC" + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(PRODUCT_LIST) + products_list = cursor.fetchall() + #print(products_list) + + productJS = json.dumps( [dict(ix) for ix in products_list] ) + print(productJS) + array3 = json.loads(productJS) + #return jsonify(products_data=products_data, products_list=array3) + return jsonify(myproudct=myproudct) +# GLOBAL_AVG = """SELECT * FROM members WHERE id > 0;""" +# +# result = pd.read_sql(GLOBAL_AVG, engine) +# print(result) +# +# cols = result.columns.difference(['Col1']) +# d = (result.groupby('Col1')[cols] +# .apply(lambda x: x.to_dict('r')) +# .reset_index(name='Other_details') +# .to_json(orient='records')) +# +# #json_data = [json.loads(row[0]) for row in result] +# +# with connection: +# with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: +# cursor.execute(GLOBAL_AVG) +# account = cursor.fetchall() +# print(account[0]["uid"]) + +# for row in account.rows: +# print(row['id'], row['uid']) +# print(account) +# # Convert the list of tuples to a list of JSON objects +# json_data = [json.loads(row[0]) for row in account] +# #print(account) +# json_data = json.dumps(account) +# print(json_data) + # connection.close() +# return jsonify(result=account, action_data=action_data, account=account) + +# GLOBAL_AVG = """SELECT * FROM members WHERE id = 1;""" +# with connection: +# with connection.cursor() as cursor: +# cursor.execute(GLOBAL_AVG) +# account = cursor.fetchone() +# return jsonify(hello="ameye world") +# # return {"account": account} + + +@app.route("/panel/auth/login", methods=["POST"]) +def start_login(): + try: + data = request.json + if not data: + return { + "message": "Please provide user details", + "data": None, + "error": "Bad request" + }, 400 + # validate input + is_validated = validate.validate_username_and_password(data.get('username'), data.get('password')) + if is_validated is not True: + return dict(message='Invalid data', data=None, error=is_validated), 400 + member = Members().login( + data["username"], + data["password"] + ) + if member: + try: + user = {} + user_data = {} + user_data["id"] = member[0] + user_data["member_id"] = member[0] + user_data["uid"] = member[1] + + # token should expire after 24 hrs + user["token"] = jwt.encode( + {"user": user_data, 'exp' : datetime.datetime.utcnow() + datetime.timedelta(minutes=3330)}, + app.config["SECRET_KEY"], + algorithm="HS256" + ) + user["room"] = member[1] + return { + "message": "Successfully fetched auth token", + "data": user + } + except Exception as e: + return { + "error": "Something went wrong", + "message": str(e) + }, 500 + return { + "message": "Error fetching auth token!, invalid email or password", + "data": None, + "error": "Unauthorized" + }, 404 + + except Exception as e: + return { + "message": "Something went wrong!", + "error": str(e), + "data": None + }, 500 + + +@app.route("/panel/auth/register", methods=["POST"]) +def start_register(): + try: + data = request.json + if not data: + return { + "message": "Please provide signup details", + "data": None, + "error": "Bad request" + }, 400 + # validate input + print(data) + is_validated = validate.validate_signup_data(data.get('firstname'), data.get('lastname'), data.get('email')) + if is_validated is not True: + return dict(message='Invalid data', data=None, error=is_validated), 400 + firstname= data.get('firstname') + lastname= data.get('lastname') + email= data.get('email') + +# mycursor = mydb.cursor() +# +# sql = "INSERT INTO customers (name, address) VALUES (%s, %s)" +# val = ("John", "Highway 21") +# mycursor.execute(sql, val) +# +# mydb.commit() + +# query = "INSERT INTO users (username, password) VALUES (?, ?)" +# values = ("john", "password123") +# cursor.execute(query, values) +# conn.commit() +# email="ameye+" + str( random.randint(1000, 99999)) + "@chiefsoft.com" +# firstname="First" + str(random.randint(10, 100)) +# lastname="Last" + str(random.randint(10, 100)); + + SELECT_INSERT = "SELECT id,uid, firstname , lastname , email FROM members_pending WHERE status = 0 AND email='" + email + "'" +# print(SELECT_INSERT) + ADJUST_PREVIOUS = "UPDATE members_pending SET status = 3 WHERE status = 0 AND email='" + email + "'" + val_update = (email) + val_insert = (email, firstname, lastname) + INSERT_MEMBERS = "INSERT INTO members_pending(email,firstname,lastname) VALUES(%s,%s,%s)" + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(ADJUST_PREVIOUS) + cursor.execute(INSERT_MEMBERS, val_insert) + +# print(SELECT_INSERT) + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(SELECT_INSERT) + select_pendingRes = cursor.fetchall() + + print(select_pendingRes) + select_pending = json.dumps( [dict(ix) for ix in select_pendingRes] ) + print(select_pending) + array3 = json.loads(select_pending) + + last_row_id = array3[0]['id'] + firstname = array3[0]['firstname'] + email_uid = array3[0]['uid'] + + +# INSERT_MEMBERS = "INSERT INTO members_pending(email,firstname,lastname) VALUES('"+email+"','"+firstname+"','"+lastname+"')" +# with connection: +# with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: +# cursor.execute(INSERT_MEMBERS) +# connection.commit() +# last_row_id=cursor.lastrowid +# print('After Insert ::: ') +# print(last_row_id) + + send_register_mail(email,email_uid,last_row_id, firstname) + return jsonify(pending_user=array3, last_row_id=last_row_id) + + except Exception as e: + return { + "message": "Something went wrong!", + "error": str(e), + "data": None + }, 500 + +def send_register_mail(signup_email, email_uid,last_row_id,firstname): +#panel_url + signup_data = {} + signup_data["id"] = last_row_id + signup_data["uid"] = email_uid + #'d8651e10-3279-4858-87da-b52936faa6f0' + + jwt_part = jwt.encode( + {"user": signup_data, 'exp' : datetime.datetime.utcnow() + datetime.timedelta(minutes=3330)}, + app.config["SECRET_KEY"], + algorithm="HS256" + ) + link_url= str(panel_url) + '/csignup/' + jwt_part + print(link_url) + #firstname ='Name001' + msg = Message( + 'verify your MERMS Account', + sender ='message@chiefsoft.com', + recipients = [signup_email,'ameye+merscopy@chiefsoft.com'] + ) + msg.body = f""" + Hello {firstname}, + You received this message for account verification + + Follow the link:{link_url} + + For any Support + Reach Out + """ + + mail.send(msg) + +# https://dev-panel.mermsemr.com/csignup/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiOTgyODkyODQyODI4NTI4OSIsInVpZCI6Ijk4Mjg5Mjg0MjgyODUyODkifSwiZXhwIjoxNzM1NTA0MDE4fQ.pDQvYUr_PGZMeMO2gr-B3DRQ7AM7IjVM5vSERNTviG4 + + +@app.route("/panel/auth/register/verify", methods=["POST"]) +def verify_register(): + data = request.json +# print(data) + vrl = data['verify_link'] + #print( vrl ) + if not vrl: + return jsonify({'message': 'Error - missing verify link'}), 403 + try: + data= jwt.decode(vrl, app.config['SECRET_KEY'], algorithms=["HS256"]) + except: + return jsonify({'status': 'INVALID', 'message': 'Link is invalid'}),403 + + country = { + "last_update": datetime.datetime.utcnow(), + "list": [ + {"code":"US", "description": "United States" }, + {"code":"NG", "description": "Nigeria" }, + ] + } + user_uid = data['user']['uid'] + #'a4b75649-e3c5-424f-bcdb-5481e625d24b' + FIND_USER_DETAIL= "SELECT email, firstname,lastname,uid FROM members_pending WHERE status = 0 AND uid::text = '"+user_uid+"'" + print(FIND_USER_DETAIL) + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(FIND_USER_DETAIL) + account = cursor.fetchall() +# print(account[0]) + + account_found = count = len( account ) + if account_found == 0 : + return jsonify({'status': "INVALID",'message': 'Error - Invalid sign up link'}), 403 + + accountRes = json.dumps( [dict(ix) for ix in account] ) + user_array = json.loads(accountRes) + + return jsonify(status="VALID", user=user_array[0], pending_uid=user_uid,country=country, verify_link=vrl) + +@app.route("/panel/auth/register/complete", methods=["POST"]) +def complete_register(): + data = request.json +# print(data) + vrl = data['verify_link'] + #print( vrl ) + if not vrl: + return jsonify({'message': 'Error - missing verify link'}), 403 + try: + pending_data = jwt.decode(vrl, app.config['SECRET_KEY'], algorithms=["HS256"]) + except: + return jsonify({'status': 'INVALID', 'message': 'Link is invalid'}),403 + + is_validated = validate.validate_complete_signup_data(data.get('username'), data.get('password'), data.get('country')) + if is_validated is not True: + return dict(message='Invalid data', data=None, error=is_validated), 400 + + print(pending_data) + username= data['username'] + password= data['password'] + country= data['country'] + + FIND_USERNAME= f"SELECT id FROM members WHERE username::text = '{username}' " + print(FIND_USERNAME) + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(FIND_USERNAME) + user_search = cursor.fetchall() + print(user_search) + #print(user_search[0][0]) + account_id = count = len( user_search ) + if account_id > 0 : + return jsonify({'status': "DUPLICATE",'message': 'Error - use another username'}), 403 + + user_uid = pending_data['user']['uid'] + #'a4b75649-e3c5-424f-bcdb-5481e625d24b' + FIND_USER_DETAIL = "SELECT firstname,lastname,email, uid FROM members_pending WHERE uid::text = '"+user_uid+"'" + COMPLETE_PENDING = "UPDATE members_pending SET status = 5 WHERE status IN (0,1,2) AND uid::text = '"+user_uid+"'" + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(FIND_USER_DETAIL) + account = cursor.fetchall() + print(account[0]) + + accountRes = json.dumps( [dict(ix) for ix in account] ) + user_array = json.loads(accountRes) + encrypt_password = Members().encrypt_password( + password + ) + + CREATE_USER = 'INSERT INTO members (username,password,account_name,firstname,lastname,country,email) VALUES(%s,%s,%s,%s,%s,%s,%s)' + create_values = (username, encrypt_password ,'ACCOUNT NAME ', user_array[0]['firstname'],user_array[0]['lastname'],country, user_array[0]['email']) + print(create_values) + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(CREATE_USER,create_values) + connection.commit() + cursor.execute(COMPLETE_PENDING) + + member = Members().login( + username, + password + ) + if member: + try: + user = {} + user_data = {} + user_data["id"] = member[0] + user_data["member_id"] = member[0] + user_data["uid"] = member[1] + + # token should expire after 24 hrs + user["token"] = jwt.encode( + {"user": user_data, 'exp' : datetime.datetime.utcnow() + datetime.timedelta(minutes=3330)}, + app.config["SECRET_KEY"], + algorithm="HS256" + ) + user["room"] = member[1] + return { + "message": "Successfully fetched auth token", + "data": user + } + except Exception as e: + return { + "error": "Something went wrong", + "message": str(e) + }, 500 + return { + "message": "Error fetching auth token!, invalid email or password", + "data": None, + "error": "Unauthorized" + }, 404 + + + return jsonify(status="VALID", user=user_array[0], pending_uid=user_uid) + + + +@app.route("/panel/auth/reset", methods=["POST"]) +def start_resetpass(): + try: + data = request.json + if not data: + return { + "message": "Please provide username ", + "data": None, + "error": "Bad request" + }, 400 + # validate input + print(data) + is_validated = validate.validate_username(data.get('username')) + if is_validated is not True: + return dict(message='Invalid data', data=None, error=is_validated), 400 + + username= data.get('username') + sql = "INSERT INTO password_reset (username) VALUES (%s)" + val = (username) + with connection: + with connection.cursor() as cursor: + cursor.execute(sql, val) + connection.commit() + # last_row_id=cursor.lastrowid + + send_resetpass_mail('ameye@chiefsoft.com') + # return jsonify(hello="ameye reset path world", last_row_id=last_row_id) + return jsonify(hello="ameye reset path world") + + except Exception as e: + return { + "message": "Something went wrong!", + "error": str(e), + "data": None + }, 500 + +def send_resetpass_mail(signup_email): + msg = Message( + 'Reset your MERMS Account', + sender ='message@chiefsoft.com', + recipients = [signup_email,'ameye+merscopy@chiefsoft.com'] + ) + msg.body = 'Hello MERMS message sent for account verification http://localhost:8090/accreset/JWT-djhgdhjgdhdggggd' + mail.send(msg) + + + +@app.route("/panel/account") +@token_required +def panel_account(current_user): + # print(current_user["user"]["uid"]) + user_uid = current_user["user"]["uid"] +# print(user_uid) + member_dash = Members().get_member_by_uid(user_uid) + print(member_dash[0]) + print(member_dash[0][0]) + return jsonify(hello=current_user) + +@app.route("/panel/account/dash") +@token_required +def dashboard(current_user): + print( current_user["user"]["uid"]) + user_uid = current_user["user"]["uid"] + FIND_USER_DETAIL= "SELECT id,uid,username,updated,email,account_name, firstname, lastname FROM members WHERE uid::text = '"+user_uid+"'" + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(FIND_USER_DETAIL) + account = cursor.fetchall() + print(account[0]) + + dash_data = { + "username": account[0]["username"], + "account_name": account[0]["account_name"], + "firstname": account[0]["firstname"], + "lastname" : account[0]["lastname"], + "email": account[0]["email"], + "updated": account[0]["updated"], + } + return jsonify(dash_data=dash_data) + +@app.route("/panel/account/products") +@token_required +def panel_products(current_user): +# products_data = { +# "last_update": datetime.datetime.utcnow(), +# "products": [ +# {"uid":"A000001","icon": "icon_product", "description": "Professional Website" , "status": 'Activate now'}, +# {"uid":"A000002","icon": "icon_product", "description": "Professional Blog" , "status": 'Activate now'}, +# {"uid":"A000003","icon": "icon_product", "description": "Business Website" , "status": 'Activate now'}, +# {"uid":"A000004","icon": "icon_product", "description": "Business Blog Site" , "status": 'Activate now'}, +# {"uid":"A000005","icon": "icon_product", "description": "OpenEmr" , "status": 'Activate now'}, +# {"uid":"A000005","icon": "icon_product", "description": "Dummy Dummy" , "status": 'Activate now'}, +# ] +# } +# print(products_data) + user_id = current_user["user"]["id"] + PRODUCT_LIST = f"""SELECT p.id,p.uid,p.product_id,p.name,p.description,p.status,p.banner, + mp.status AS prov_status, + (CASE WHEN mp.status =6 THEN 'Preparing' WHEN mp.status=7 THEN 'Active' ELSE 'Activate Now' END) AS status_text + FROM products p + LEFT JOIN members_products mp ON mp.product_id = p.product_id + AND mp.member_id ={user_id} + ORDER BY p.id ASC""" + print( PRODUCT_LIST ) + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(PRODUCT_LIST) + products_list = cursor.fetchall() + #print(products_list) + productJS = json.dumps( [dict(ix) for ix in products_list] ) + #print(productJS) + #print(productJS) + array3 = json.loads(productJS) + return jsonify( products_list=array3, last_update= datetime.datetime.utcnow()) + #return jsonify(products_data=products_data, products_list=productJS) + +@app.route("/panel/account/bar") +@token_required +def recent_bar(current_user): + bar_data = { + "last_update": datetime.datetime.utcnow(), + "top_bar": [ + {"id": "1", "description": "Contacts" , "last_update": "10-10-2010 11:00 AM", "value": random.randint(0, 10) , "data_span":'Last 2 months'}, + {"id": "2", "description": "Site Traffic" , "last_update": "10-10-2010 11:30 AM", "value": random.randint(0, 10), "data_span":'Past 12 hours'}, + {"id": "3", "description": "Appointments" , "last_update": "10-12-2010 11:30 AM", "value": random.randint(0, 10), "data_span":'Last 14 days'}, + {"id": "4", "description": "Purchases" , "last_update": "10-12-2010 11:30 AM", "value": random.randint(0, 10), "data_span":'Last 3 months'}, + ] + } + return jsonify(bar_data=bar_data) + +@app.route("/panel/account/calendar") +@token_required +def calendar_data(current_user): +# let send 60 days of data in only - cache heavy + dList = [] + sample_range = random.randint(20, 60) + for x in range(sample_range): + timeMin = random.randint(1440, 2880) + calDate = datetime.datetime.utcnow() + datetime.timedelta(minutes=timeMin * random.randint(0, 20)) + new_l = {"uid":"425611f2-c692-4404-b93d-76ca7a5ce7"+str(x), "title": "Calendar Random Item on " + str(x) , "start": calDate, "category":random.randint(1, 4) } + dList.append(new_l) + + calendar_data = { + "last_update": datetime.datetime.utcnow(), + "category": [ + {"cid": "1", "description": "category 01" }, + {"cid": "2", "description": "category 02" }, + {"cid": "3", "description": "category 03" }, + {"cid": "4", "description": "category 04" }, + ], + "list" : dList + } + return jsonify(bar_data=calendar_data) + + +@app.route("/panel/account/actions") +@token_required +def recent_actions(current_user): + print( current_user["user"]["uid"]) + user_id = current_user["user"]["id"] + # FIND_USER_DETAIL= "SELECT id,uid,username,updated,email,account_name, firstname, lastname FROM members WHERE uid::text = '"+user_uid+"'" + # RECENT_ACTIONS = "SELECT * FROM members_actions WHERE member_id = " + user_id + " ORDER by id DESC LIMIT 4" + # RECENT_ACTIONS = "SELECT * FROM members_actions WHERE member_id::text = %s ORDER by id DESC LIMIT 4" +# print(user_id) + RECENT_ACTIONS = f"""SELECT id AS no, + action_label AS description, + added::text AS date,status + FROM members_actions + WHERE member_id = {user_id} + ORDER by id DESC LIMIT 4""" + actionVal = (user_id) + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(RECENT_ACTIONS) + recent_actions = cursor.fetchall() + + print(recent_actions) + actionJS = json.dumps( [dict(ix) for ix in recent_actions] ) + print(actionJS) + array3 = json.loads(actionJS) + print(array3) + + action_data = { + "recent_actions" : recent_actions, + "last_update": datetime.datetime.utcnow(), + "initial": random.randint(0, 10), + "processing": random.randint(0, 10), + "verifying" : random.randint(0, 10), + "completed" : random.randint(0, 10), + "actions" :array3 + } + return jsonify(action_data=action_data, recent_actions=array3) + +@app.route("/panel/account/products/url") +@token_required +def product_urls(current_user): + print( current_user["user"]["uid"]) + user_uid = current_user["user"]["uid"] + user_id = current_user["user"]["id"] + PRODUCT_URL = f"""SELECT mp.id AS no, p.name AS description, + mp.added::text AS date, 'https://'||''||mp.internal_url AS url, mp.product_id, + (CASE WHEN mp.status=6 THEN 'Preparing' WHEN mp.status =7 THEN 'Active' ELSE 'Unknown' END) AS status, + 'https://' AS http FROM members_products mp + LEFT JOIN products p ON p.product_id =mp.product_id + WHERE mp.member_id = {user_id}""" + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(PRODUCT_URL) + recent_actions = cursor.fetchall() + + print(recent_actions) + actionJS = json.dumps( [dict(ix) for ix in recent_actions] ) + print(actionJS) + array3 = json.loads(actionJS) + print(array3) + + url_data = { + "last_update": datetime.datetime.utcnow(), + "url": array3 + } + return jsonify(url_data=url_data) + +@app.route("/panel/account/payments") +@token_required +def account_payments(current_user): + payments = { + "last_update": datetime.datetime.utcnow(), + "url": [ + {"no": "1", "description": "Welcome to MERMS" , "date": "10-10-2010 11:00 AM", "status": 'completed'}, + {"no": "2", "description": "Personal Blog Setup" , "date": "10-10-2010 11:30 AM", "status": 'processing'}, + {"no": "3", "description": "Web Traffic Analysis" , "date": "10-12-2010 11:30 AM", "status": 'verifying'}, + ] + } + return jsonify(payments_data=payments_data) +##. Description Date Status "10-10-2021 10 AM" + +@app.route("/panel/myproduct/dash") +@token_required +def myproduct(current_user): + product_id = request.args.get('product_id') + print(product_id) + if not product_id: + return { + "message": "Please provide product_id", + "data": None, + "error": "Bad request" + }, 400 + + print(product_id) + member_id = current_user['user']['member_id'] + myproduct_data = myproduct_detail(member_id , product_id) + return jsonify(myproduct_data=myproduct_data) + +@app.route("/panel/myproduct/provision") +@token_required +def myproduct_provision(current_user): + product_id = request.args.get('product_id') + product_uid = request.args.get('product_subscription_uid') + print(product_uid) + if not product_uid: + return { + "message": "Please provide product_subscription_uid ", + "data": None, + "error": "Bad request" + }, 400 + + if not product_id: + return { + "message": "Please provide product_id ", + "data": None, + "error": "Bad request" + }, 400 + + print(product_id) + member_id = current_user['user']['member_id'] + myproduct_data = myproduct_detail(member_id , product_id) + #product_uid = '06a8e774-c8f2-4d6f-993b-1b9ba0cf538e' + SELECT_PROVISION_ACTIVITIES = f"SELECT id,action,added::text AS date from provision_actions WHERE product_uid='{product_uid}' ORDER BY id DESC LIMIT 10" + print(SELECT_PROVISION_ACTIVITIES) + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(SELECT_PROVISION_ACTIVITIES) + select_pendingRes = cursor.fetchall() + + print(select_pendingRes) + activitiesData = json.dumps( [dict(ix) for ix in select_pendingRes] ) + print(activitiesData) + provision_activities = json.loads(activitiesData) + + provision = { + "last_update": datetime.datetime.utcnow(), + "percent_completed" : random.randint(10, 99), + "activities": provision_activities + } + return jsonify(provision=provision,myproduct_data=myproduct_data) + +@app.route("/panel/myproduct/subscription", methods=["POST"]) +@token_required +def subscription_start(current_user): + print( current_user ) + #{'user': {'id': 5, 'member_id': 5, 'uid': '1f92e1ae-c084-4622-9e7c-dfeedf698c58'}, 'exp': 1736713281} + data = request.json + product_id = data.get('product_id') + if not product_id: + return { + "message": "Please provide product_id", + "data": None, + "error": "Bad request" + }, 400 + + #request.args.get('product_id') + FIND_PRODUCT= "SELECT uid,product_id,name,description,banner,status FROM products WHERE product_id='"+product_id+"'" + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(FIND_PRODUCT) + product = cursor.fetchall() +# print(product[0]["uid"]) + + product_found = count = len( product ) + if product_found == 0 : + return jsonify({'status': "INVALID",'message': 'Error - Invalid product'}), 403 +#INITIAL_PRODUCT_URL + member_id = current_user['user']['member_id'] +# internal_url = str(random.randint(10000, 99999)) + "." + product_id + ".mermsemr.com" + internal_url = str(random.randint(10000, 99999)) + ".devprov.mermsemr.com" + INSERT_NEW_PRODUCT ="INSERT INTO members_products (member_id ,product_id,status,internal_url) VALUES (%s, %s, %s, %s)" + val_insert = (member_id, product_id,6,internal_url) + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(INSERT_NEW_PRODUCT,val_insert) + + SELECT_LAST_ENTRY = f"SELECT id, uid AS product_uid FROM members_products WHERE internal_url='{internal_url}'" + print(SELECT_LAST_ENTRY) + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(SELECT_LAST_ENTRY) + select_pendingRes = cursor.fetchall() + # Assume data is there + addProvisionAction(member_id,select_pendingRes[0][1],'Started initial provision - '+ internal_url) # Create action Entry + print(select_pendingRes) + actionJS = json.dumps( [dict(ix) for ix in select_pendingRes] ) + print(actionJS) + myproduct_data = json.loads(actionJS) + print(myproduct_data) + + return jsonify(myproduct_data=myproduct_data) + +def addProvisionAction(member_id,product_uid,action): + INSERT_NEW_ACTION ="INSERT INTO provision_actions (member_id,product_uid,action) VALUES (%s, %s, %s)" + val_insert = (member_id,product_uid,action) + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(INSERT_NEW_ACTION,val_insert) + + return 0 + +@app.route("/panel/contacts") +@token_required +def site_contacts(current_user): + cat_list = ['A000002','A000004','A000001','A000003'] + SUPPORTED_CATEGORY = "SELECT name AS title,product_id from products WHERE product_id IN ('A000002','A000004','A000001','A000003')" + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(SUPPORTED_CATEGORY) + select_Res = cursor.fetchall() + + print(select_Res) + contacts_category = json.dumps( [dict(ix) for ix in select_Res] ) + print(contacts_category) + array_cat = json.loads(contacts_category) + + dList = [] + sample_range = random.randint(20, 60) + for x in range(sample_range): + calDate = datetime.datetime.utcnow() + datetime.timedelta(minutes=180 * random.randint(1, 20)) + new_l = { + "uid":"425611f2-c692-4404-b93d-76ca7a5ce7"+str(x), + "title": "Calendar Random Item on " + str(x) , + "category" : cat_list[ random.randint(0, 3) ], + "added": calDate, + "sender": "Firstname Lastname" + str(random.randint(1, 4)), + "message" : dummy_message() + } + dList.append(new_l) + + calendar_data = { + "last_update": datetime.datetime.utcnow(), + "category" : array_cat, + "contacts" : dList + } + return jsonify(calendar_data=calendar_data) + +@app.route("/panel/settings") +def user_settings(): + + + + settings_data = { + "external_links": [ + { + "facebook": "" , + "twitter": "" , + "google": "" , + "other_web": "" , + "linkedin": "" , + } + ], + "personal": [ + { + "firstname": "" , + "lastname": "", + "phone": "", + "email": "", + "account_name": "", + "country": "", + "username": "", + "address": "", + "state": "", + "city": "", + "postal_code": "" + }, + ], + } + return jsonify(settings_data=settings_data) + + +def myproduct_detail(member_id ,product_id): + FIND_PRODUCT= "SELECT uid,product_id,name,description,banner,status FROM products WHERE product_id='"+product_id+"'" + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(FIND_PRODUCT) + product = cursor.fetchall() +# print(product[0]["uid"]) + status = product[0]["status"]; + + MEMBER_PRODUCT = f"SELECT uid AS product_subscription_uid , status , internal_url FROM members_products WHERE member_id= {member_id} AND product_id = '" + product_id + "'" + val_select = (member_id, product_id) + with connection: + with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor: + cursor.execute(MEMBER_PRODUCT) + myproduct = cursor.fetchall() + + product_found = count = len( product ) + mproduct_found = count = len( myproduct ) + product_subscription_uid ='' + internal_url = '' + if mproduct_found == 1 : + status = myproduct[0]['status'] + product_subscription_uid = myproduct[0]['product_subscription_uid'] + internal_url = myproduct[0]['internal_url'] + myproduct_data = { + "product_subscription_uid": product_subscription_uid, + "internal_url":internal_url, + "status": status, + "product_id": product[0]["product_id"], + "product_uid": product[0]["uid"], + "banner": product[0]["banner"], + "product_name": product[0]["name"], + "description": "Product Description - Commitment is something that comes from understanding that everything has its price and then having the willingness to pay that price. This is important because nobody wants to put significant effort into something, only to find out after the fact that the price was too high.The price is something not necessarily defined as financial. It could be time, effort, sacrifice, money or perhaps, something else.", + "title": product[0]["description"], + "subscription_text" : "Start with your goals in mind and then work possible.ith yand Goals. If the plan doesn’t support the vision then change it!", + "promotion_text": "Start Free Today !", + "price_text" : "90 days free and 3.95/Month", + } + return myproduct_data diff --git a/services/web/project/config.py b/services/web/project/config.py new file mode 100644 index 0000000..90eba5d --- /dev/null +++ b/services/web/project/config.py @@ -0,0 +1,11 @@ +import os + + +basedir = os.path.abspath(os.path.dirname(__file__)) + + +class Config(object): + SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL", "sqlite://") + SQLALCHEMY_TRACK_MODIFICATIONS = False + STATIC_FOLDER = f"{os.getenv('APP_FOLDER')}/project/static" + MEDIA_FOLDER = f"{os.getenv('APP_FOLDER')}/project/media" diff --git a/services/web/project/media/.gitkeep b/services/web/project/media/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/services/web/project/models/members.py b/services/web/project/models/members.py new file mode 100644 index 0000000..f240672 --- /dev/null +++ b/services/web/project/models/members.py @@ -0,0 +1,95 @@ +"""Application Members Models""" +import os +# bson, +from dotenv import load_dotenv +from werkzeug.security import generate_password_hash, check_password_hash +import psycopg2 +from psycopg2.extras import NamedTupleCursor +import pandas as pd + +load_dotenv() + +# DATABASE_URL=os.environ.get('DATABASE_URL') or 'mongodb://localhost:27017/myDatabase' +# print(DATABASE_URL) +# client = MongoClient(DATABASE_URL) +# db = client.myDatabase + +dataUrl = os.getenv("DATABASE_URL") +db = psycopg2.connect(dataUrl) + + +class Members: + """User Model""" + def __init__(self): + return + + + def get_member_by_uid(self, user_uid): + """Get a user by uid""" + #user = db.members.find_one({"uid": user_uid, "active": True}) + GLOBAL_AVG = "SELECT username,email,account_name,firstname,lastname FROM members WHERE uid::text = '" + user_uid + "'" + #print(GLOBAL_AVG) + with db: + with db.cursor() as cursor: + cursor.execute(GLOBAL_AVG) + account = cursor.fetchall() + #return jsonify(hello="ameye world") + # Convert to DataFrame + df = pd.DataFrame(account, columns=[desc[0] for desc in db.description]) + print(df) + if not account: + return + return account + + +# def get_by_id(self, user_id): +# """Get a user by id""" +# user = db.users.find_one({"_id": bson.ObjectId(user_id), "active": True}) +# if not user: +# return +# user["_id"] = str(user["_id"]) +# user.pop("password") +# return user + + def get_by_username(self, username): + """Get a user by username""" + print(db) +# sqv = "SELECT * FROM members WHERE id=%s" +# x=["1"] + sqv = "SELECT * FROM members WHERE username='"+username+"'" + with db: + with db.cursor(cursor_factory=NamedTupleCursor) as cursor: + #with db.cursor() as cursor: + cursor.execute(sqv) + #cursor.execute(sqv, x) + member = cursor.fetchall() + + if not member: + return + #member["_id"] = member[0] + #str(member[0]) + return member[0] + #return user + + def encrypt_password(self, password): + """Encrypt password""" + return generate_password_hash(password) + + def login(self, username, password): + """Login a user""" + member = self.get_by_username(username) +# print('yyyyyyyyyyy---mmmmmm') +# print( member ) +# print('yyyyyyyyyyy---xxxxxxxxx') + +# for column in member: +# print(f"{column}") + +# print( member[3] ) +# print(self.encrypt_password(password)) + + if not member or not check_password_hash(member[3], password): + return + #member.pop(3) + #member[3]='' + return member diff --git a/services/web/project/static/hello.txt b/services/web/project/static/hello.txt new file mode 100644 index 0000000..32aad8c --- /dev/null +++ b/services/web/project/static/hello.txt @@ -0,0 +1 @@ +hi! diff --git a/services/web/project/validate/validate.py b/services/web/project/validate/validate.py new file mode 100644 index 0000000..3d4625d --- /dev/null +++ b/services/web/project/validate/validate.py @@ -0,0 +1,45 @@ +"""Validator Module""" +import re +#from bson.objectid import ObjectId + +def validate(data, regex): + """Custom Validator""" + return True if re.match(regex, data) else False + +def validate_password(password: str): + """Password Validator""" + # print(password) + #reg = r"\b^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!#%*?&]{8,20}$\b" + reg = r"\b^[A-Za-z0-9@#$%^&+=]{8,}\b" + return validate(password, reg) + +def validate_username(username: str): + if not 6 <= len(username.split(' ')) <= 20: + return { + 'name': 'Username must be between 6 and 15 words' + } + return True + +def validate_signup_data(firstname,lastname,email): + return True + +def validate_complete_signup_data(username,password,country): + return True + +def validate_username_and_password(username, password): + """Username and Password Validator""" + if not (username and password): + return { + 'username': 'Username is required', + 'password': 'Password is required' + } + if not validate_username(username): + return { + 'username': 'Username is invalid' + } + if not validate_password(password): + return { + 'password': 'Password is invalid, Should be at least 8 characters with \ + upper and lower case letters, numbers and special characters' + } + return True diff --git a/services/web/requirements.txt b/services/web/requirements.txt new file mode 100644 index 0000000..2e96f08 --- /dev/null +++ b/services/web/requirements.txt @@ -0,0 +1,15 @@ +Flask==2.3.2 +Flask-SQLAlchemy==3.0.3 +gunicorn==20.1.0 +psycopg2-binary==2.9.6 +flask-smorest==0.42.3 +python-dotenv +pyjwt +pillow +flask-cors +pandas +flasgger +sqlalchemy +flask-socketio +Flask-Mail +pycountry \ No newline at end of file