├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── assets ├── Automato Logo - squiggle.svg ├── automato.svg └── header.png ├── automato-front.bat ├── automato-serve.bat ├── automato-service.bat ├── automato.bat ├── back ├── app.py ├── automato_winservice.py ├── config.py ├── consumer.py ├── csv_load.py ├── json-load.py ├── migrations │ ├── README │ ├── alembic.ini │ ├── env.py │ ├── script.py.mako │ └── versions │ │ ├── 033d3c3ffe79_.py │ │ ├── b04011c66f30_.py │ │ └── bf3c038f77b8_.py ├── model.py ├── requirements.txt ├── routes │ ├── __init__.py │ ├── city.py │ ├── contacts.py │ ├── groups.py │ ├── job.py │ ├── scraper.py │ ├── settings.py │ ├── tag.py │ └── templates.py ├── run.py ├── scraper │ └── __init__.py ├── task.db └── tasks.py └── front ├── .editorconfig ├── README.md ├── assets ├── README.md ├── b-style.scss └── main.css ├── components ├── EditContact.vue ├── EditTemplate.vue ├── Logo.vue ├── NewContact.vue ├── NewGroup.vue ├── NewImport.vue ├── NewJob.vue ├── NewScraper.vue ├── NewTemplate.vue └── README.md ├── jsconfig.json ├── layouts ├── README.md └── default.vue ├── middleware └── README.md ├── nuxt.config.js ├── package-lock.json ├── package.json ├── pages ├── README.md ├── contacts.vue ├── groups.vue ├── index.vue ├── jobs.vue ├── master.vue ├── master │ ├── city.vue │ ├── index.vue │ └── tag.vue ├── scrapers.vue ├── settings.vue └── templates.vue ├── plugins ├── README.md ├── buefy.js └── feather.js ├── static ├── README.md ├── favicon.ico ├── main.scss └── uploads │ └── templates │ ├── 000000.jpg │ ├── 11111.png │ ├── 700rapid (1).jpg │ ├── IMG_4467_1024x1024.webp │ ├── KENDRICK_NAVY_F_1024x1024.webp │ ├── image-indiamart-resized.jpg │ ├── itmanan_png_white.png │ ├── mememe.jpg │ └── sign-mememem.jpg └── store └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | uENV/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | 107 | # VSCode 108 | .vscode 109 | 110 | 111 | back/user-data-wp/ 112 | 113 | static/uploads/ 114 | 115 | # dependencies 116 | node_modules 117 | 118 | # logs 119 | npm-debug.log 120 | 121 | # Nuxt build 122 | .nuxt 123 | 124 | # Nuxt generate 125 | dist -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at padamsethia5@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Padam Sethia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 |

5 | 6 | 7 | 8 |
9 |

10 | 11 |

Web Application to automate sending Whatsapp Messages , SMS & Email campaigns( WIP ).

12 | 13 | ## Features 14 | 15 | * Scrape business data from Google Maps , IndiaMart ( more coming soon ) 16 | * Mange your contacts , tag them to send specialized campaigns. 17 | * Manage and edit contact/business . 18 | * Import/ Export Data ( WIP ) out of Automato 19 | * Setup templates for campaigns 20 | * Send Whatsapp and SMS campaigns without any extra cost 21 | 22 | ## Installation & Setup 23 | 24 | 25 | *NOTE:* You'll need to use python3. 26 | 27 | To install requirements : 28 | 29 | > Clone or download https://github.com/padamsethia/automato 30 | 31 | > Run pip install -r requirements.txt in automato/back 32 | 33 | > Run npm install in automato/front 34 | 35 | > npm run dev to run the frontend at automato/front 36 | 37 | > python run.py to run the backend at automato/back 38 | 39 | > python huey-consumer.py tasks.huey to run the consumer at automato/back 40 | 41 | Automato requires the following softwares to work , please follow the setup from the offical docs. 42 | 43 | * [Selenium](https://www.seleniumhq.org/) 44 | * [MySQL](https://www.mysql.com/downloads/) as backend database 45 | 46 | 47 | ## Issues and Requests 48 | For any issue or requests , kindly use Github Issues. 49 | 50 | ## Projects Used 51 | * [Bulma CSS](http://getskeleton.com) 52 | * Flask 53 | * Buefy 54 | * Vue.js 55 | * SQLAlchemy 56 | * Albemic 57 | 58 | ## License 59 | This project is licensed under the [MIT License](./LICENSE). 60 | -------------------------------------------------------------------------------- /assets/Automato Logo - squiggle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/automato.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/highoncarbs/automato/5a8a7e8cae613962684c725edf4bf88e324ef775/assets/header.png -------------------------------------------------------------------------------- /automato-front.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cmd /k "cd /d %CD%\front & npm run start &" -------------------------------------------------------------------------------- /automato-serve.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cmd /k "cd /d %CD%\back\ENV\Scripts\ & activate & cd /d %CD%\back & python run.py" -------------------------------------------------------------------------------- /automato-service.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cmd /k "cd /d %CD%\back\ENV\Scripts\ & activate & cd /d %CD%\back & python .\ENV\Lib\site-packages\huey\bin\huey_consumer.py tasks.huey" -------------------------------------------------------------------------------- /automato.bat: -------------------------------------------------------------------------------- 1 | @start "" /B %SystemRoot%\System32\cmd.exe /C "%CD%\automato-front.bat" 2 | @start "" /B %SystemRoot%\System32\cmd.exe /C "%CD%\automato-serve.bat" 3 | @start "" /B %SystemRoot%\System32\cmd.exe /C "%CD%\automato-service.bat" 4 | -------------------------------------------------------------------------------- /back/app.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from flask import Flask, request , jsonify 4 | from flask_sqlalchemy import SQLAlchemy 5 | from flask_migrate import Migrate 6 | from config import Config 7 | from werkzeug.debug import DebuggedApplication 8 | from flask_marshmallow import Marshmallow 9 | from flask_cors import CORS 10 | import json 11 | from sqlalchemy.exc import IntegrityError 12 | app = Flask(__name__) 13 | app.config.from_object(Config) 14 | CORS(app) 15 | 16 | UPLOAD_FOLDER = os.path.abspath(app.config['UPLOAD_FOLDER']) 17 | 18 | 19 | db = SQLAlchemy(app) 20 | migrate = Migrate(app ,db) 21 | ma = Marshmallow(app) 22 | 23 | from routes import templates , contacts , city , tag , scraper , groups , job , settings 24 | -------------------------------------------------------------------------------- /back/automato_winservice.py: -------------------------------------------------------------------------------- 1 | import win32serviceutil 2 | import win32service 3 | import win32event 4 | import servicemanager 5 | import logging 6 | import time 7 | from app import app, db 8 | from gevent.pywsgi import WSGIServer 9 | import socket 10 | import webbrowser 11 | 12 | logging.basicConfig( 13 | filename='.\\automato-service.log', 14 | level=logging.DEBUG, 15 | format='[automato-service] %(levelname)-7.7s %(message)s' 16 | ) 17 | 18 | global http_server 19 | 20 | class AutomatoSvc(win32serviceutil.ServiceFramework): 21 | _svc_name_ = "Automato-Service" 22 | _svc_display_name_ = "Automato Service" 23 | 24 | def __init__(self,args): 25 | win32serviceutil.ServiceFramework.__init__(self,args) 26 | self.stop_event = win32event.CreateEvent(None,0,0,None) 27 | socket.setdefaulttimeout(60) 28 | self.stop_requested = False 29 | 30 | def SvcStop(self): 31 | self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 32 | win32event.SetEvent(self.stop_event) 33 | logging.info('Stopping service ...') 34 | http_server.stop() 35 | self.stop_requested = True 36 | 37 | def SvcDoRun(self): 38 | servicemanager.LogMsg( 39 | servicemanager.EVENTLOG_INFORMATION_TYPE, 40 | servicemanager.PYS_SERVICE_STARTED, 41 | (self._svc_name_,'') 42 | ) 43 | self.main() 44 | 45 | def main(self): 46 | logging.info('Automato Windows Service Started') 47 | # Simulate a main loop 48 | db.create_all() 49 | logging.info('Starting server') 50 | global http_server 51 | http_server = WSGIServer(('0.0.0.0' , 80) , app) 52 | http_server.serve_forever() 53 | webbrowser.open_new_tab('localhost') 54 | 55 | if __name__ == '__main__': 56 | win32serviceutil.HandleCommandLine(AutomatoSvc) -------------------------------------------------------------------------------- /back/config.py: -------------------------------------------------------------------------------- 1 | # Config for automato 2 | # WTF config 3 | class Config(object): 4 | 5 | WTF_CSRF_ENABLED = True 6 | SECRET_KEY = 'the_very_secure_secret_security_key_that_no_will_ever_guess' 7 | 8 | # MySQL Config 9 | 10 | SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:alpine@127.0.0.1/reautomato' 11 | SQLALCHEMY_TRACK_MODIFICATIONS = False 12 | RABBITMQ_HOST = "localhost" 13 | 14 | WEBDRIVER_PATH = r"C:\Users\padam\Downloads\chromedriver_win32\chromedriver.exe" 15 | TESTING = False 16 | 17 | UPLOAD_FOLDER = './../front/static/uploads' 18 | 19 | -------------------------------------------------------------------------------- /back/consumer.py: -------------------------------------------------------------------------------- 1 | from google import huey 2 | 3 | if __name__ == "__main__": 4 | 5 | print('Running') -------------------------------------------------------------------------------- /back/csv_load.py: -------------------------------------------------------------------------------- 1 | import csv , os 2 | from app import db 3 | from model import Contact , City , Tag 4 | from sqlalchemy.exc import IntegrityError 5 | 6 | file_json = r"" 7 | 8 | path = os.path.abspath(file_json) 9 | with open(path) as f: 10 | data = csv.reader(f) 11 | 12 | tags = ['suit', 'dupatta', 'home', 'skirt', 'pant', 'palazzo', 'tops', 'saree', 'blouse' , 'fabric' , 'dress material' , 'bag' , 'kurti' , 'bedsheet' , 'kurta'] 13 | 14 | def check(string, sub_str): 15 | if (string.find(sub_str) != -1): 16 | return sub_str 17 | 18 | 19 | for item in data: 20 | 21 | name = item['contacts_name'] + '-' + item['contacts_company'] 22 | contact = str(item['contact_ph_country']) + str(item['contacts_mobile1']) 23 | city = item['contact_city'] 24 | state = item['contact_state'] 25 | country = item['country_name'] 26 | enquiry = str(item['contact_last_product']).lower() 27 | tag = [y for y in (check(enquiry, x) for x in tags) if y is not None] 28 | tag.append('indiamart') 29 | 30 | if city == "": 31 | if state == "": 32 | city = country 33 | else: 34 | city = state 35 | 36 | 37 | # Checks for empty values 38 | 39 | try: 40 | city_obj = City.query.filter_by(name=str(city)).first() 41 | 42 | if city_obj: 43 | pass 44 | else: 45 | city_obj = City(str(city), str(state), str(country)) 46 | db.session.add(city_obj) 47 | 48 | 49 | con_obj = Contact(name , contact , "" , "" , "" , city_obj) 50 | db.session.add(con_obj) 51 | 52 | for item in tag: 53 | tag_obj = Tag.query.filter_by(name=item).first() 54 | if tag_obj: 55 | pass 56 | else: 57 | tag_obj = Tag(str(item)) 58 | db.session.add(tag_obj) 59 | 60 | con_obj.tag_contact.append(tag_obj) 61 | 62 | db.session.commit() 63 | print('Added Data ---- ' + name , city , contact) 64 | except IntegrityError: 65 | db.session.rollback() 66 | print('Duplicate ----- '+ name , city , contact) 67 | pass 68 | except Exception as e: 69 | db.session.rollback() 70 | print('Oh No ---------' , name, city , contact ) 71 | pass 72 | 73 | 74 | import requests 75 | def check_api(): 76 | data = json.loads(requests.get("http://api.geonames.org/postalCodeSearchJSON?placename=mumbai&username=padam&maxrows=100"))['postalCodes'] 77 | area = [ x['placeName'] for x in data] 78 | print(area) 79 | 80 | if __name__ == "__main__": 81 | check_api 82 | -------------------------------------------------------------------------------- /back/json-load.py: -------------------------------------------------------------------------------- 1 | import json , os 2 | from app import db 3 | from model import Contact , City , Tag 4 | from sqlalchemy.exc import IntegrityError 5 | 6 | file_json = r"C:\Users\Padam\Desktop\inmart.txt" 7 | 8 | path = os.path.abspath(file_json) 9 | with open(path) as f: 10 | data = json.load(f)['result'] 11 | 12 | tags = ['suit', 'dupatta', 'home', 'skirt', 'pant', 'palazzo', 'tops', 'saree', 'blouse' , 'fabric' , 'dress material' , 'bag' , 'kurti' , 'bedsheet' , 'kurta'] 13 | 14 | def check(string, sub_str): 15 | if (string.find(sub_str) != -1): 16 | return sub_str 17 | 18 | 19 | for item in data: 20 | if item['contacts_company'] is None: 21 | item['contacts_company'] = "" 22 | name = item['contacts_name'] + '-' + item['contacts_company'] 23 | contact = str(item['contact_ph_country']) + str(item['contacts_mobile1']) 24 | city = item['contact_city'] 25 | state = item['contact_state'] 26 | country = item['country_name'] 27 | enquiry = str(item['contact_last_product']).lower() 28 | tag = [y for y in (check(enquiry, x) for x in tags) if y is not None] 29 | tag.append('indiamart') 30 | 31 | if city == "": 32 | if state == "": 33 | city = country 34 | else: 35 | city = state 36 | 37 | 38 | # Checks for empty values 39 | 40 | try: 41 | city_obj = City.query.filter_by(name=str(city)).first() 42 | 43 | if city_obj: 44 | pass 45 | else: 46 | city_obj = City(str(city), str(state), str(country)) 47 | db.session.add(city_obj) 48 | 49 | 50 | con_obj = Contact(name , contact , "" , "" , "" , city_obj) 51 | db.session.add(con_obj) 52 | 53 | for item in tag: 54 | tag_obj = Tag.query.filter_by(name=item).first() 55 | if tag_obj: 56 | pass 57 | else: 58 | tag_obj = Tag(str(item)) 59 | db.session.add(tag_obj) 60 | 61 | con_obj.tag_contact.append(tag_obj) 62 | 63 | db.session.commit() 64 | print('Added Data ---- ' + name , city , contact) 65 | except IntegrityError: 66 | db.session.rollback() 67 | print('Duplicate ----- '+ name , city , contact) 68 | pass 69 | except Exception as e: 70 | db.session.rollback() 71 | print('Oh No ---------' , name, city , contact ) 72 | pass 73 | 74 | 75 | import requests 76 | def check_api(): 77 | data = json.loads(requests.get("http://api.geonames.org/postalCodeSearchJSON?placename=mumbai&username=padam&maxrows=100"))['postalCodes'] 78 | area = [ x['placeName'] for x in data] 79 | print(area) 80 | 81 | if __name__ == "__main__": 82 | check_api 83 | -------------------------------------------------------------------------------- /back/migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /back/migrations/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [handler_console] 38 | class = StreamHandler 39 | args = (sys.stderr,) 40 | level = NOTSET 41 | formatter = generic 42 | 43 | [formatter_generic] 44 | format = %(levelname)-5.5s [%(name)s] %(message)s 45 | datefmt = %H:%M:%S 46 | -------------------------------------------------------------------------------- /back/migrations/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | import logging 4 | from logging.config import fileConfig 5 | 6 | from sqlalchemy import engine_from_config 7 | from sqlalchemy import pool 8 | 9 | from alembic import context 10 | 11 | # this is the Alembic Config object, which provides 12 | # access to the values within the .ini file in use. 13 | config = context.config 14 | 15 | # Interpret the config file for Python logging. 16 | # This line sets up loggers basically. 17 | fileConfig(config.config_file_name) 18 | logger = logging.getLogger('alembic.env') 19 | 20 | # add your model's MetaData object here 21 | # for 'autogenerate' support 22 | # from myapp import mymodel 23 | # target_metadata = mymodel.Base.metadata 24 | from flask import current_app 25 | config.set_main_option( 26 | 'sqlalchemy.url', current_app.config.get( 27 | 'SQLALCHEMY_DATABASE_URI').replace('%', '%%')) 28 | target_metadata = current_app.extensions['migrate'].db.metadata 29 | 30 | # other values from the config, defined by the needs of env.py, 31 | # can be acquired: 32 | # my_important_option = config.get_main_option("my_important_option") 33 | # ... etc. 34 | 35 | 36 | def run_migrations_offline(): 37 | """Run migrations in 'offline' mode. 38 | 39 | This configures the context with just a URL 40 | and not an Engine, though an Engine is acceptable 41 | here as well. By skipping the Engine creation 42 | we don't even need a DBAPI to be available. 43 | 44 | Calls to context.execute() here emit the given string to the 45 | script output. 46 | 47 | """ 48 | url = config.get_main_option("sqlalchemy.url") 49 | context.configure( 50 | url=url, target_metadata=target_metadata, literal_binds=True 51 | ) 52 | 53 | with context.begin_transaction(): 54 | context.run_migrations() 55 | 56 | 57 | def run_migrations_online(): 58 | """Run migrations in 'online' mode. 59 | 60 | In this scenario we need to create an Engine 61 | and associate a connection with the context. 62 | 63 | """ 64 | 65 | # this callback is used to prevent an auto-migration from being generated 66 | # when there are no changes to the schema 67 | # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html 68 | def process_revision_directives(context, revision, directives): 69 | if getattr(config.cmd_opts, 'autogenerate', False): 70 | script = directives[0] 71 | if script.upgrade_ops.is_empty(): 72 | directives[:] = [] 73 | logger.info('No changes in schema detected.') 74 | 75 | connectable = engine_from_config( 76 | config.get_section(config.config_ini_section), 77 | prefix='sqlalchemy.', 78 | poolclass=pool.NullPool, 79 | ) 80 | 81 | with connectable.connect() as connection: 82 | context.configure( 83 | connection=connection, 84 | target_metadata=target_metadata, 85 | process_revision_directives=process_revision_directives, 86 | **current_app.extensions['migrate'].configure_args 87 | ) 88 | 89 | with context.begin_transaction(): 90 | context.run_migrations() 91 | 92 | 93 | if context.is_offline_mode(): 94 | run_migrations_offline() 95 | else: 96 | run_migrations_online() 97 | -------------------------------------------------------------------------------- /back/migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /back/migrations/versions/033d3c3ffe79_.py: -------------------------------------------------------------------------------- 1 | """empty message 2 | 3 | Revision ID: 033d3c3ffe79 4 | Revises: b04011c66f30 5 | Create Date: 2020-01-07 18:28:41.146110 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '033d3c3ffe79' 14 | down_revision = 'b04011c66f30' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('job', sa.Column('meta', sa.String(length=250), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('job', 'meta') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /back/migrations/versions/b04011c66f30_.py: -------------------------------------------------------------------------------- 1 | """empty message 2 | 3 | Revision ID: b04011c66f30 4 | Revises: 5 | Create Date: 2020-01-06 11:39:46.970064 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import mysql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'b04011c66f30' 14 | down_revision = None 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('club_job', 22 | sa.Column('id', sa.Integer(), nullable=False), 23 | sa.Column('club_id', sa.Integer(), nullable=True), 24 | sa.Column('job_id', sa.Integer(), nullable=True), 25 | sa.ForeignKeyConstraint(['club_id'], ['club.id'], ondelete='SET NULL'), 26 | sa.ForeignKeyConstraint(['job_id'], ['job.id'], ondelete='SET NULL'), 27 | sa.PrimaryKeyConstraint('id') 28 | ) 29 | op.create_table('template_job', 30 | sa.Column('id', sa.Integer(), nullable=False), 31 | sa.Column('template_id', sa.Integer(), nullable=True), 32 | sa.Column('job_id', sa.Integer(), nullable=True), 33 | sa.ForeignKeyConstraint(['job_id'], ['job.id'], ondelete='SET NULL'), 34 | sa.ForeignKeyConstraint(['template_id'], ['template.id'], ondelete='SET NULL'), 35 | sa.PrimaryKeyConstraint('id') 36 | ) 37 | op.drop_column('job', 'provider') 38 | # ### end Alembic commands ### 39 | 40 | 41 | def downgrade(): 42 | # ### commands auto generated by Alembic - please adjust! ### 43 | op.add_column('job', sa.Column('provider', mysql.VARCHAR(length=50), nullable=True)) 44 | op.drop_table('template_job') 45 | op.drop_table('club_job') 46 | # ### end Alembic commands ### 47 | -------------------------------------------------------------------------------- /back/migrations/versions/bf3c038f77b8_.py: -------------------------------------------------------------------------------- 1 | """empty message 2 | 3 | Revision ID: bf3c038f77b8 4 | Revises: 033d3c3ffe79 5 | Create Date: 2020-01-13 17:08:16.904072 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'bf3c038f77b8' 14 | down_revision = '033d3c3ffe79' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('scrape', sa.Column('meta', sa.String(length=250), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('scrape', 'meta') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /back/model.py: -------------------------------------------------------------------------------- 1 | from app import db 2 | from app import ma 3 | from datetime import datetime 4 | 5 | from marshmallow_sqlalchemy import field_for 6 | from marshmallow import fields 7 | 8 | 9 | 10 | 11 | # Contact Template 12 | class Contact(db.Model): 13 | id = db.Column(db.Integer, primary_key=True) 14 | name = db.Column(db.String(250), nullable=False) 15 | contact_one = db.Column(db.String(50) , unique=True , default= None, nullable = False) 16 | contact_two = db.Column(db.String(50) , default="") 17 | address = db.Column(db.String(500) , default="") 18 | email = db.Column(db.String(200) , default="") 19 | city = db.relationship( 20 | 'City', cascade="all,delete", secondary='city_contact', backref='city_contact', lazy='joined') 21 | 22 | def __init__(self, name, contact_one, contact_two, address, email, city): 23 | self.name = name 24 | self.contact_one = contact_one 25 | self.contact_two = contact_two 26 | self.address = address 27 | self.email = email 28 | self.city.append(city) 29 | 30 | 31 | 32 | db.Table('city_contact', 33 | db.Column('id', db.Integer, primary_key=True), 34 | db.Column('city_id', db.Integer, db.ForeignKey( 35 | 'city.id', ondelete='SET NULL')), 36 | db.Column('contact_id', db.Integer, db.ForeignKey( 37 | 'contact.id', ondelete='SET NULL')) 38 | ) 39 | 40 | 41 | 42 | 43 | class City(db.Model): 44 | id = db.Column(db.Integer, primary_key=True) 45 | name = db.Column(db.String(100) ) 46 | state = db.Column(db.String(100) , default = "") 47 | country = db.Column(db.String(100) , default = "") 48 | 49 | def __init__(self, name, state, country): 50 | self.name = name 51 | self.state = state 52 | self.country = country 53 | 54 | class CitySchema(ma.ModelSchema): 55 | id = field_for(City, 'id', dump_only=True) 56 | name = field_for(City, 'name', dump_only=True) 57 | state = field_for(City, 'state', dump_only=True) 58 | country = field_for(City, 'country', dump_only=True) 59 | 60 | class meta: 61 | model = City 62 | 63 | 64 | db.Table('tag_contact', 65 | db.Column('id', db.Integer, primary_key=True), 66 | db.Column('tag_id', db.Integer, db.ForeignKey( 67 | 'tag.id', ondelete='SET NULL')), 68 | db.Column('contact_id', db.Integer, db.ForeignKey( 69 | 'contact.id', ondelete='SET NULL')) 70 | ) 71 | 72 | 73 | class Tag(db.Model): 74 | id = db.Column(db.Integer, primary_key=True) 75 | name = db.Column(db.String(100)) 76 | contact = db.relationship( 77 | 'Contact', cascade="all,delete", secondary='tag_contact', backref='tag_contact', lazy='joined') 78 | def __init__(self, name): 79 | self.name = name 80 | 81 | class TagSchema(ma.ModelSchema): 82 | id = field_for(Tag, 'id', dump_only=True) 83 | name = field_for(Tag, 'name', dump_only=True) 84 | 85 | class meta: 86 | model = Tag 87 | 88 | class Template(db.Model): 89 | id = db.Column(db.Integer, primary_key=True) 90 | name = db.Column(db.String(100) , unique = True) 91 | path = db.Column(db.String(200) , default = "") 92 | message = db.Column(db.String(500) , default= "") 93 | filetype = db.Column(db.String(100)) 94 | 95 | def __init__(self, name, message, filetype): 96 | self.name = name 97 | self.message = message 98 | self.filetype = filetype 99 | 100 | class Club(db.Model): 101 | id = db.Column(db.Integer, primary_key=True) 102 | name = db.Column(db.String(100)) 103 | contact = db.relationship( 104 | 'Contact', cascade="all,delete", secondary='club_contact', backref='club_contact', lazy='joined') 105 | tag = db.relationship( 106 | 'Tag', cascade="all,delete", secondary='club_tag', backref='club_tag', lazy='joined') 107 | city = db.relationship( 108 | 'City', cascade="all,delete", secondary='club_city', backref='club_city', lazy='joined') 109 | 110 | def __init__(self, name): 111 | self.name = name 112 | 113 | def get_contacts(self): 114 | return len(self.contact) 115 | 116 | db.Table('club_contact', 117 | db.Column('id', db.Integer, primary_key=True), 118 | db.Column('club_id', db.Integer, db.ForeignKey( 119 | 'club.id', ondelete='SET NULL')), 120 | db.Column('contact_id', db.Integer, db.ForeignKey( 121 | 'contact.id', ondelete='SET NULL')) 122 | ) 123 | db.Table('club_tag', 124 | db.Column('id', db.Integer, primary_key=True), 125 | db.Column('club_id', db.Integer, db.ForeignKey( 126 | 'club.id', ondelete='SET NULL')), 127 | db.Column('tag_id', db.Integer, db.ForeignKey( 128 | 'tag.id', ondelete='SET NULL')) 129 | ) 130 | db.Table('club_city', 131 | db.Column('id', db.Integer, primary_key=True), 132 | db.Column('club_id', db.Integer, db.ForeignKey( 133 | 'club.id', ondelete='SET NULL')), 134 | db.Column('city_id', db.Integer, db.ForeignKey( 135 | 'city.id', ondelete='SET NULL')) 136 | ) 137 | 138 | class TemplateSchema(ma.ModelSchema): 139 | id = field_for(Template, 'id', dump_only=True) 140 | name = field_for(Template, 'name', dump_only=True) 141 | path = field_for(Template, 'path', dump_only=True) 142 | message = field_for(Template, 'message', dump_only=True) 143 | filetype = field_for(Template, 'filetype', dump_only=True) 144 | 145 | class meta: 146 | model = Template 147 | 148 | class Scrape(db.Model): 149 | id = db.Column(db.Integer, primary_key=True) 150 | city = db.relationship( 151 | 'City', cascade="all,delete", secondary='city_scrape', backref='city_scrape', lazy='joined') 152 | status = db.Column(db.String(10) , default="start") 153 | timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) 154 | tag = db.relationship( 155 | 'Tag', cascade="all,delete", secondary='tag_scrape', backref='tag_scrape', lazy='joined') 156 | meta= db.Column(db.String(250) , default="") 157 | def __init__(self, city): 158 | self.city.append(city) 159 | 160 | 161 | db.Table('tag_scrape', 162 | db.Column('id', db.Integer, primary_key=True), 163 | db.Column('tag_id', db.Integer, db.ForeignKey( 164 | 'tag.id', ondelete='SET NULL')), 165 | db.Column('scrape_id', db.Integer, db.ForeignKey( 166 | 'scrape.id', ondelete='SET NULL')) 167 | ) 168 | db.Table('city_scrape', 169 | db.Column('id', db.Integer, primary_key=True), 170 | db.Column('city_id', db.Integer, db.ForeignKey( 171 | 'city.id', ondelete='SET NULL')), 172 | db.Column('scrape_id', db.Integer, db.ForeignKey( 173 | 'scrape.id', ondelete='SET NULL')) 174 | ) 175 | 176 | class ScrapeSchema(ma.ModelSchema): 177 | id = field_for(Scrape, 'id', dump_only=True) 178 | status = field_for(Scrape, 'status', dump_only=True) 179 | timestamp = field_for(Scrape, 'timestamp', dump_only=True) 180 | city = ma.Nested(CitySchema , many = True) 181 | tag = ma.Nested(TagSchema, many=True) 182 | 183 | 184 | class ContactSchema(ma.ModelSchema): 185 | id = field_for(Contact, 'id', dump_only=True) 186 | name = field_for(Contact, 'name', dump_only=True) 187 | contact_one = field_for(Contact, 'contact_one', dump_only=True) 188 | contact_two = field_for(Contact, 'contact_two', dump_only=True) 189 | address = field_for(Contact, 'address', dump_only=True) 190 | email = field_for(Contact, 'email', dump_only=True) 191 | city = ma.Nested(CitySchema , many = True) 192 | tag_contact = ma.Nested(TagSchema , many = True) 193 | class meta: 194 | model = Contact 195 | 196 | class ContactBaseSchema(ma.ModelSchema): 197 | id = field_for(Contact, 'id', dump_only=True) 198 | name = field_for(Contact, 'name', dump_only=True) 199 | contact_one = field_for(Contact, 'contact_one', dump_only=True) 200 | contact_two = field_for(Contact, 'contact_two', dump_only=True) 201 | address = field_for(Contact, 'address', dump_only=True) 202 | email = field_for(Contact, 'email', dump_only=True) 203 | city = ma.Nested(CitySchema , many = True) 204 | class meta: 205 | model = Contact 206 | 207 | 208 | db.Table('club_job', 209 | db.Column('id', db.Integer, primary_key=True), 210 | db.Column('club_id', db.Integer, db.ForeignKey( 211 | 'club.id', ondelete='SET NULL')), 212 | db.Column('job_id', db.Integer, db.ForeignKey( 213 | 'job.id', ondelete='SET NULL')) 214 | ) 215 | db.Table('template_job', 216 | db.Column('id', db.Integer, primary_key=True), 217 | db.Column('template_id', db.Integer, db.ForeignKey( 218 | 'template.id', ondelete='SET NULL')), 219 | db.Column('job_id', db.Integer, db.ForeignKey( 220 | 'job.id', ondelete='SET NULL')) 221 | ) 222 | 223 | 224 | class ClubSchema(ma.ModelSchema): 225 | id = field_for(Club, 'id', dump_only=True) 226 | name = field_for(Club, 'name', dump_only=True) 227 | tag = ma.Nested(TagSchema , many= True) 228 | city = ma.Nested(CitySchema , many= True) 229 | total = fields.Method('get_contacts_len') 230 | 231 | def get_contacts_len(self, obj): 232 | # base_city = Contact.query.join(City, Contact.city) 233 | # base_tag = Contact.query.join(Tag, Contact.tag_contact) 234 | 235 | # base = base_city.union(base_tag) 236 | if (len(obj.tag) == 0 and len(obj.city) != 0): 237 | total = len([t for x in obj.city for t in x.city_contact]) 238 | 239 | elif (len(obj.city) == 0 and len(obj.tag) != 0): 240 | total = len([c for x in obj.tag for c in x.contact]) 241 | 242 | elif (len(obj.city) != 0 and len(obj.tag) != 0): 243 | 244 | total_city = [t for x in obj.city for t in x.city_contact] 245 | total_tag = [c for x in obj.tag for c in x.contact] 246 | total = len( set(total_city) & set(total_tag)) 247 | else: 248 | total = 0 249 | return { 'contact' : len(obj.contact) , 'tag': total } 250 | 251 | class meta: 252 | model = Club 253 | 254 | class ClubContactSchema(ma.ModelSchema): 255 | id = field_for(Club, 'id', dump_only=True) 256 | total_contact = fields.Method('get_contacts') 257 | 258 | def get_contacts(self, obj): 259 | # base_city = Contact.query.join(City, Contact.city) 260 | # base_tag = Contact.query.join(Tag, Contact.tag_contact) 261 | 262 | # base = base_city.union(base_tag) 263 | if (len(obj.tag) == 0 and len(obj.city) != 0): 264 | total = [t for x in obj.city for t in x.city_contact] 265 | 266 | elif (len(obj.city) == 0 and len(obj.tag) != 0): 267 | total = [c for x in obj.tag for c in x.contact] 268 | 269 | elif (len(obj.city) != 0 and len(obj.tag) != 0): 270 | 271 | total_city = [t for x in obj.city for t in x.city_contact] 272 | total_tag = [c for x in obj.tag for c in x.contact] 273 | total = set(total_city) & set(total_tag) 274 | else: 275 | total = 0 276 | 277 | schema = ContactBaseSchema(many=True) 278 | 279 | return schema.dump(total) 280 | 281 | 282 | class meta: 283 | model = Club 284 | 285 | 286 | class Job(db.Model): 287 | id = db.Column(db.Integer, primary_key=True) 288 | club = db.relationship( 289 | 'Club', cascade="all,delete", secondary='club_job', backref='club_job', lazy='joined') 290 | status = db.Column(db.String(10) , default="start") 291 | timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) 292 | template = db.relationship( 293 | 'Template', cascade="all,delete", secondary='template_job', backref='template_job', lazy='joined') 294 | meta = db.Column( db.String(250) , default = None) 295 | 296 | 297 | class JobSchema(ma.ModelSchema): 298 | id = field_for(Scrape, 'id', dump_only=True) 299 | status = field_for(Job, 'status', dump_only=True) 300 | timestamp = field_for(Job, 'timestamp', dump_only=True) 301 | meta = field_for(Job, 'meta', dump_only=True) 302 | club = ma.Nested(ClubSchema , many = True) 303 | template = ma.Nested(TemplateSchema, many=True) 304 | -------------------------------------------------------------------------------- /back/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/highoncarbs/automato/5a8a7e8cae613962684c725edf4bf88e324ef775/back/requirements.txt -------------------------------------------------------------------------------- /back/routes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/highoncarbs/automato/5a8a7e8cae613962684c725edf4bf88e324ef775/back/routes/__init__.py -------------------------------------------------------------------------------- /back/routes/city.py: -------------------------------------------------------------------------------- 1 | from app import app , jsonify , request , json , db , ma 2 | 3 | from model import City, CitySchema 4 | 5 | @app.route('/get/city', methods=['GET']) 6 | def get_city(): 7 | if request.method == 'GET': 8 | 9 | data_schema = CitySchema(many=True) 10 | data = City.query.all() 11 | json_data = data_schema.dump(data) 12 | return jsonify(json_data) 13 | 14 | 15 | @app.route('/add/city', methods=['POST']) 16 | def add_city(): 17 | if request.method == 'POST': 18 | payload = request.json 19 | print(payload) 20 | if len(payload['name']) != 0: 21 | 22 | check_data = City.query.filter_by(name=payload['name'].lower().strip()) 23 | if check_data.first(): 24 | return jsonify({'message': 'Data - '+check_data.first().name+' already exists.'}) 25 | else: 26 | try: 27 | if 'state' not in payload.keys(): 28 | payload['state'] = "" 29 | if 'country' not in payload.keys(): 30 | payload['country'] = "" 31 | 32 | new_data = City( 33 | payload['name'].lower().strip() , payload['state'].lower().strip() , payload['country'].lower().strip()) 34 | 35 | db.session.add(new_data) 36 | db.session.commit() 37 | json_data = { 'id' : new_data.id , 'name' : new_data.name} 38 | return jsonify({'success': 'Data Added', 'data' : json_data}) 39 | 40 | except Exception as e: 41 | print(str(e)) 42 | db.session.rollback() 43 | db.session.close() 44 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 45 | else: 46 | return jsonify({'message': 'Empty Data.'}) 47 | 48 | else: 49 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 50 | 51 | 52 | @app.route('/edit/city', methods=['POST']) 53 | def edit_city(): 54 | if request.method == 'POST': 55 | 56 | payload = request.json 57 | if payload['name'] is not None: 58 | 59 | check_data = City.query.filter_by( 60 | name=payload['name'].lower().strip()).first() 61 | if check_data and check_data.name != payload['name'].lower().strip(): 62 | return jsonify({'message': 'Data - '+check_data.name+' already exists.'}) 63 | else: 64 | try: 65 | new_data = City.query.filter_by( 66 | id=payload['id']).first() 67 | new_data.name = payload['name'].lower().strip() 68 | new_data.state = payload['state'].lower().strip() 69 | new_data.country = payload['country'].lower().strip() 70 | db.session.commit() 71 | return jsonify({'success': 'Data Updated'}) 72 | 73 | except Exception as e: 74 | print(str(e)) 75 | 76 | db.session.rollback() 77 | db.session.close() 78 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 79 | else: 80 | return jsonify({'message': 'Empty Data.'}) 81 | 82 | else: 83 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 84 | 85 | 86 | @app.route('/delete/city', methods=['POST']) 87 | def delete_city(): 88 | if request.method == 'POST': 89 | payload = request.json 90 | check_data = City.query.filter_by(id=payload['id']) 91 | if check_data.first(): 92 | if len(check_data.first().city_contact) is int(0): 93 | try: 94 | check_data.delete() 95 | db.session.commit() 96 | return jsonify({'success': 'Data deleted'}) 97 | except Exception as e: 98 | db.session.rollback() 99 | db.session.close() 100 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 101 | else: 102 | return jsonify({'message': 'Cannot delete data. Being used by other master.'}) 103 | 104 | else: 105 | return jsonify({'message': 'Data does not exist.'}) 106 | 107 | else: 108 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 109 | -------------------------------------------------------------------------------- /back/routes/contacts.py: -------------------------------------------------------------------------------- 1 | import os 2 | from app import app, jsonify, request, json, UPLOAD_FOLDER 3 | from app import db 4 | from app import ma 5 | 6 | from sqlalchemy.exc import IntegrityError 7 | import csv 8 | 9 | 10 | from model import Contact, ContactSchema, ContactBaseSchema, City, Tag 11 | 12 | 13 | @app.route('/get/contacts', methods=['POST']) 14 | def get_contacts(): 15 | payload = request.json 16 | schema = ContactBaseSchema(many=True) 17 | sort_order = payload['sort_order'] 18 | sort_by = payload['sort_by'] 19 | page = payload['page'] 20 | 21 | data = Contact.query 22 | if sort_by == 'name' and sort_order == "desc": 23 | chain_data = data.order_by(Contact.name.desc()) 24 | if sort_by == 'name' and sort_order == "asc": 25 | chain_data = data.order_by(Contact.name.asc()) 26 | 27 | # if sort_by == 'city' and sort_order == "desc": 28 | # chain_data = data. .join(model.ClassificationItem).order_by(Contact.city[0].name.desc()) 29 | 30 | # if sort_by == 'city' and sort_order == "asc": 31 | # chain_data = data.order_by(Contact.city[0].name.asc()) 32 | 33 | if sort_by == 'id' and sort_order == "asc": 34 | chain_data = data.order_by(Contact.id.asc()) 35 | 36 | if sort_by == 'id' and sort_order == "desc": 37 | chain_data = data.order_by(Contact.id.desc()) 38 | 39 | if sort_by == 'contact_one' and sort_order == "asc": 40 | chain_data = data.order_by(Contact.contact_one.asc()) 41 | 42 | if sort_by == 'contact_one' and sort_order == "desc": 43 | chain_data = data.order_by(Contact.contact_one.desc()) 44 | 45 | data = chain_data.paginate(page, 20, False).items 46 | json_data = schema.dump(data) 47 | return jsonify(json_data) 48 | 49 | 50 | @app.route('/get/contacts/', methods=['POST']) 51 | def get_contacts_by_id(id): 52 | schema = ContactSchema() 53 | data = Contact.query.filter_by(id=int(id)).first() 54 | json_data = schema.dump(data) 55 | return jsonify(json_data) 56 | 57 | 58 | @app.route('/add/contacts', methods=['POST']) 59 | def add_contact(): 60 | payload = request.json 61 | try: 62 | 63 | city = City.query.filter_by(id=int(payload['city'])).first() 64 | new_data = Contact(payload['name'], payload['contact_one'], 65 | payload['contact_two'], payload['address'], payload['email'], city) 66 | 67 | if (len(payload['tags']) is not 0): 68 | 69 | for item in payload['tags']: 70 | data = Tag.query.filter_by( 71 | id=item['id']).first() 72 | new_data.tag_contact.append(data) 73 | 74 | db.session.add(new_data) 75 | db.session.commit() 76 | return jsonify({'success': "Data added"}) 77 | 78 | except Exception as e: 79 | # return Exception for duplicate data 80 | print(str(e)) 81 | return jsonify({'message': "Something unexpected happened"}) 82 | 83 | 84 | @app.route('/edit/contacts/', methods=['POST']) 85 | def edit_contact(id): 86 | payload = request.json 87 | try: 88 | new_data = Contact.query.filter_by(id=int(id)).first() 89 | new_data.tag_contact = [] 90 | 91 | city = City.query.filter_by(id=int(payload['city'])).first() 92 | new_data.name = payload['name'] 93 | new_data.contact_one = payload['contact_one'] 94 | new_data.contact_two = payload['contact_two'] 95 | new_data.address = payload['address'] 96 | new_data.email = payload['email'] 97 | 98 | if (len(payload['tags']) is not 0): 99 | 100 | for item in payload['tags']: 101 | data = Tag.query.filter_by( 102 | id=item['id']).first() 103 | new_data.tag_contact.append(data) 104 | 105 | db.session.commit() 106 | return jsonify({'success': "Data added"}) 107 | 108 | except Exception as e: 109 | # return Exception for duplicate data 110 | print(str(e)) 111 | return jsonify({'message': "Something unexpected happened"}) 112 | 113 | 114 | @app.route('/delete/contacts/', methods=['POST']) 115 | def delete_contact(id): 116 | 117 | try: 118 | new_data = Contact.query.filter_by(id=int(id)) 119 | if new_data.first(): 120 | new_data.delete() 121 | db.session.commit() 122 | return jsonify({'success': "Data Deleted"}) 123 | 124 | else: 125 | return jsonify({'message': "Data doesn't exists"}) 126 | except Exception as e: 127 | # return Exception for duplicate data 128 | print(str(e)) 129 | 130 | 131 | @app.route('/upload/contacts', methods=['POST']) 132 | def upload_contact(): 133 | file = request.files['file'] 134 | tags = json.loads(request.form['data']) 135 | try: 136 | if file: 137 | filename = file.filename 138 | foldertemp = os.path.join( 139 | UPLOAD_FOLDER, 'contacts') 140 | 141 | if os.path.exists(foldertemp): 142 | filetemp = os.path.join( 143 | foldertemp, filename) 144 | file.save(filetemp) 145 | else: 146 | 147 | os.makedirs(foldertemp) 148 | 149 | filetemp = os.path.join( 150 | foldertemp, filename) 151 | file.save(filetemp) 152 | 153 | with open(filetemp, mode="r") as csv_file: 154 | 155 | csv_data = list(csv.reader(csv_file, delimiter=",")) 156 | 157 | for item in csv_data: 158 | name = item[0] + '-' + item[1] 159 | contact_one = str(item[6]) 160 | contact_two = str(item[7]) 161 | address = str(item[2]) 162 | city = ''.join(i for i in item[3] if not i.isdigit()).lower().strip() 163 | 164 | state = item[4] 165 | country = item[5] 166 | email = item[8] 167 | tag = tags 168 | city = city.split('-')[0].split('(')[0].split(',')[0].title() 169 | 170 | 171 | 172 | if len(contact_one) == 10: 173 | contact_one = '91' + contact_one 174 | 175 | if city == "": 176 | if state == "": 177 | city = country.title() 178 | else: 179 | city = state.title() 180 | 181 | if item[0] is not "": 182 | 183 | try: 184 | city_obj = City.query.filter_by(name=str(city)).first() 185 | 186 | if city_obj: 187 | pass 188 | else: 189 | 190 | city_obj = City(str(city), str(state), str(country)) 191 | db.session.add(city_obj) 192 | 193 | con_obj = Contact(name , contact_one , contact_two , address , email , city_obj) 194 | db.session.add(con_obj) 195 | 196 | for item in tag: 197 | tag_obj = Tag.query.filter_by( 198 | name=item['name']).first() 199 | if tag_obj: 200 | pass 201 | else: 202 | tag_obj = Tag(str(item['name'])) 203 | db.session.add(tag_obj) 204 | 205 | con_obj.tag_contact.append(tag_obj) 206 | db.session.commit() 207 | except IntegrityError as e: 208 | db.session.rollback() 209 | 210 | try: 211 | con_obj = Contact.query.filter_by(contact_one=contact_one).first() 212 | con_obj.city = [] 213 | db.session.commit() 214 | con_obj.city.append(city_obj) 215 | con_obj.name = name 216 | con_obj.address = address 217 | con_obj.email = email 218 | for item in tag: 219 | tag_obj = Tag.query.filter_by( 220 | name=item['name']).first() 221 | if tag_obj: 222 | pass 223 | else: 224 | tag_obj = Tag(str(item['name'])) 225 | db.session.add(tag_obj) 226 | 227 | con_obj.tag_contact.append(tag_obj) 228 | db.session.commit() 229 | 230 | except Exception as f: 231 | db.session.rollback() 232 | 233 | except Exception as e: 234 | db.session.rollback() 235 | 236 | 237 | 238 | return jsonify({'success': 'Data Added'}) 239 | 240 | except Exception as e: 241 | # return Exception for duplicate data 242 | print(str(e)) 243 | return jsonify({'message': 'Returns error'}) 244 | -------------------------------------------------------------------------------- /back/routes/groups.py: -------------------------------------------------------------------------------- 1 | from app import app , jsonify , request , json , db , ma 2 | 3 | from model import Club, ClubSchema , Contact , Tag , City 4 | 5 | 6 | @app.route('/get/club', methods=['GET']) 7 | def get_club(): 8 | if request.method == 'GET': 9 | 10 | data_schema = ClubSchema(many=True) 11 | data = Club.query.all() 12 | print(data) 13 | json_data = data_schema.dump(data) 14 | return jsonify(json_data) 15 | 16 | 17 | @app.route('/add/club', methods=['POST']) 18 | def add_club(): 19 | if request.method == 'POST': 20 | payload = request.json 21 | print(payload) 22 | if len(payload['name']) != 0: 23 | 24 | check_data = Club.query.filter_by(name=payload['name'].lower().strip()) 25 | if check_data.first(): 26 | return jsonify({'message': 'Data - '+check_data.first().name+' already exists.'}) 27 | else: 28 | try: 29 | new_data = Club( 30 | payload['name'].lower().strip()) 31 | if (len(payload['contacts']) != 0): 32 | for item in payload['contacts']: 33 | print(item) 34 | data = Contact.query.filter_by(id=int(item['id'])).first() 35 | new_data.contact.append(data) 36 | 37 | db.session.add(new_data) 38 | db.session.commit() 39 | return jsonify({'success': 'Data Added'}) 40 | 41 | else: 42 | tag = payload['tags'] 43 | city = payload['city'] 44 | for item in tag: 45 | print(item) 46 | data = Tag.query.filter_by(id=int(item['id'])).first() 47 | new_data.tag.append(data) 48 | 49 | for item in city: 50 | data = City.query.filter_by(id=int(item['id'])).first() 51 | new_data.city.append(data) 52 | 53 | 54 | db.session.commit() 55 | return jsonify({'success': 'Data Added'}) 56 | 57 | except Exception as e: 58 | print(str(e)) 59 | db.session.rollback() 60 | db.session.close() 61 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 62 | else: 63 | return jsonify({'message': 'Empty Data.'}) 64 | 65 | else: 66 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 67 | 68 | 69 | @app.route('/edit/club', methods=['POST']) 70 | def edit_club(): 71 | if request.method == 'POST': 72 | 73 | payload = request.json 74 | if payload['name'] is not None: 75 | 76 | check_data = Club.query.filter_by( 77 | name=payload['name'].lower().strip()).first() 78 | if check_data and check_data.name != payload['name'].lower().strip(): 79 | return jsonify({'message': 'Data - '+check_data.name+' already exists.'}) 80 | else: 81 | try: 82 | new_data = Club.query.filter_by( 83 | id=payload['id']).first() 84 | new_data.name = payload['name'].lower().strip() 85 | new_data.state = payload['state'].lower().strip() 86 | new_data.country = payload['country'].lower().strip() 87 | db.session.commit() 88 | return jsonify({'success': 'Data Updated'}) 89 | 90 | except Exception as e: 91 | print(str(e)) 92 | 93 | db.session.rollback() 94 | db.session.close() 95 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 96 | else: 97 | return jsonify({'message': 'Empty Data.'}) 98 | 99 | else: 100 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 101 | 102 | 103 | @app.route('/delete/club/', methods=['POST']) 104 | def delete_club(id): 105 | if request.method == 'POST': 106 | payload = request.json 107 | check_data = Club.query.filter_by(id=int(id)) 108 | if check_data.first(): 109 | try: 110 | check_data.delete() 111 | db.session.commit() 112 | return jsonify({'success': 'Data deleted'}) 113 | except Exception as e: 114 | db.session.rollback() 115 | db.session.close() 116 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 117 | else: 118 | return jsonify({'message': 'Data does not exist.'}) 119 | 120 | else: 121 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 122 | -------------------------------------------------------------------------------- /back/routes/job.py: -------------------------------------------------------------------------------- 1 | from app import app , jsonify , request , json , db , ma 2 | 3 | from model import Job, JobSchema , Club , Template , ClubContactSchema 4 | 5 | import tasks 6 | @app.route('/get/job', methods=['GET']) 7 | def get_job(): 8 | if request.method == 'GET': 9 | 10 | data_schema = JobSchema(many=True) 11 | data = Job.query.all() 12 | json_data = data_schema.dump(data) 13 | 14 | return jsonify(json_data) 15 | 16 | 17 | 18 | @app.route('/run/job', methods=['POST']) 19 | def run_job(): 20 | payload = request.json 21 | print(payload) 22 | job = Job.query.filter_by(id=int(payload['curr_id'])).first() 23 | meta = json.loads(job.meta) 24 | meta['timer'] = payload['timer'] 25 | job.meta = json.dumps(meta) 26 | db.session.commit() 27 | try: 28 | task = tasks.whatsapp(job.id) 29 | print("heheheheheheheeh" + task.args) 30 | job.status = "running" 31 | db.session.commit() 32 | return jsonify({'success': 'Job started'}) 33 | except Exception as e: 34 | return jsonify({'message': 'Unable to run job. Check logs'}) 35 | 36 | 37 | @app.route('/run/job/sms', methods=['POST']) 38 | def run_job_sms(): 39 | payload = request.json 40 | job = Job.query.filter_by(id=int(payload['curr_id'])).first() 41 | try: 42 | task = tasks.messages(job.id) 43 | return jsonify({'success': 'Job started'}) 44 | except Exception as e: 45 | return jsonify({'message': 'Unable to run job. Check logs'}) 46 | 47 | 48 | @app.route('/add/job', methods=['POST']) 49 | def add_job(): 50 | if request.method == 'POST': 51 | payload = request.json 52 | print(payload) 53 | if payload: 54 | try: 55 | if (len(payload['club']) is not 0): 56 | template = Template.query.filter_by(id = int(payload['template'])).first() 57 | new_data = Job() 58 | new_data.template.append(template) 59 | meta = {'type': payload['type']} 60 | new_data.meta = json.dumps(meta) 61 | for item in payload['club']: 62 | print(item) 63 | data = Club.query.filter_by( 64 | id=item['id']).first() 65 | new_data.club.append(data) 66 | 67 | db.session.add(new_data) 68 | db.session.commit() 69 | return jsonify({'success': 'Data Added'}) 70 | 71 | except Exception as e: 72 | print(str(e)) 73 | db.session.rollback() 74 | db.session.close() 75 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 76 | else: 77 | return jsonify({'message': 'Empty Data.'}) 78 | 79 | else: 80 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 81 | 82 | 83 | @app.route('/edit/job', methods=['POST']) 84 | def edit_job(): 85 | if request.method == 'POST': 86 | 87 | payload = request.json 88 | if payload['name'] is not None: 89 | 90 | check_data = Job.query.filter_by( 91 | name=payload['name'].lower().strip()).first() 92 | if check_data and check_data.name != payload['name'].lower().strip(): 93 | return jsonify({'message': 'Data - '+check_data.name+' already exists.'}) 94 | else: 95 | try: 96 | new_data = Job.query.filter_by( 97 | id=payload['id']).first() 98 | new_data.name = payload['name'].lower().strip() 99 | new_data.state = payload['state'].lower().strip() 100 | new_data.country = payload['country'].lower().strip() 101 | db.session.commit() 102 | return jsonify({'success': 'Data Updated'}) 103 | 104 | except Exception as e: 105 | print(str(e)) 106 | 107 | db.session.rollback() 108 | db.session.close() 109 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 110 | else: 111 | return jsonify({'message': 'Empty Data.'}) 112 | 113 | else: 114 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 115 | 116 | 117 | 118 | @app.route('/delete/job/', methods=['POST']) 119 | def delete_job(id): 120 | 121 | try: 122 | new_data = Job.query.filter_by(id = int(id)) 123 | if new_data.first(): 124 | new_data.delete() 125 | db.session.commit() 126 | return jsonify({'success': "Data Deleted"}) 127 | 128 | else: 129 | return jsonify({'message': "Data doesn't exists"}) 130 | except Exception as e: 131 | # return Exception for duplicate data 132 | print(str(e)) -------------------------------------------------------------------------------- /back/routes/scraper.py: -------------------------------------------------------------------------------- 1 | from app import app , jsonify , request , json , db , ma 2 | 3 | from model import Scrape, ScrapeSchema ,City , Tag 4 | 5 | import tasks 6 | 7 | @app.route('/get/scraper', methods=['GET']) 8 | def get_scraper(): 9 | if request.method == 'GET': 10 | 11 | data_schema = ScrapeSchema(many=True) 12 | data = Scrape.query.all() 13 | json_data = data_schema.dump(data) 14 | return jsonify(json_data) 15 | 16 | 17 | @app.route('/run/scraper/', methods=['GET']) 18 | def run_scraper(id): 19 | task = tasks.scrape_google(id) 20 | return jsonify({'success' : 'Scraper started'}) 21 | 22 | @app.route('/add/scraper', methods=['POST']) 23 | def add_scraper(): 24 | if request.method == 'POST': 25 | payload = request.json 26 | print(payload) 27 | if payload: 28 | try: 29 | if (len(payload['tags']) is not 0): 30 | city = City.query.filter_by(id = int(payload['city'])).first() 31 | check_data = Scrape.query.join(City, Scrape.city).filter(Scrape.city.any(City.id == city.id)).first() 32 | meta = {} 33 | meta['detail'] = str(payload['detail']) 34 | if (check_data): 35 | tags = check_data.tag 36 | check_data.meta = json.dumps(meta) 37 | for item in payload['tags']: 38 | if item not in tags: 39 | data = Tag.query.filter_by( 40 | id=item['id']).first() 41 | check_data.tag.append(data) 42 | else: 43 | new_data = Scrape(city) 44 | meta = {} 45 | meta['detail'] = str(payload['detail']) 46 | new_data.meta = json.dumps(meta) 47 | for item in payload['tags']: 48 | data = Tag.query.filter_by( 49 | id=item['id']).first() 50 | new_data.tag.append(data) 51 | db.session.add(new_data) 52 | db.session.commit() 53 | return jsonify({'success': 'Data Added'}) 54 | 55 | except Exception as e: 56 | print(str(e)) 57 | db.session.rollback() 58 | db.session.close() 59 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 60 | else: 61 | return jsonify({'message': 'Empty Data.'}) 62 | 63 | else: 64 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 65 | 66 | 67 | @app.route('/edit/scraper', methods=['POST']) 68 | def edit_scraper(): 69 | if request.method == 'POST': 70 | 71 | payload = request.json 72 | if payload['name'] is not None: 73 | 74 | check_data = Scrape.query.filter_by( 75 | name=payload['name'].lower().strip()).first() 76 | if check_data and check_data.name != payload['name'].lower().strip(): 77 | return jsonify({'message': 'Data - '+check_data.name+' already exists.'}) 78 | else: 79 | try: 80 | new_data = Scrape.query.filter_by( 81 | id=payload['id']).first() 82 | new_data.name = payload['name'].lower().strip() 83 | new_data.state = payload['state'].lower().strip() 84 | new_data.country = payload['country'].lower().strip() 85 | db.session.commit() 86 | return jsonify({'success': 'Data Updated'}) 87 | 88 | except Exception as e: 89 | print(str(e)) 90 | 91 | db.session.rollback() 92 | db.session.close() 93 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 94 | else: 95 | return jsonify({'message': 'Empty Data.'}) 96 | 97 | else: 98 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 99 | 100 | 101 | @app.route('/delete/scraper/', methods=['POST']) 102 | def delete_scraper(id): 103 | if request.method == 'POST': 104 | payload = request.json 105 | check_data = Scrape.query.filter_by(id=int(id)) 106 | if check_data.first(): 107 | try: 108 | check_data.delete() 109 | db.session.commit() 110 | return jsonify({'success': 'Data deleted'}) 111 | except Exception as e: 112 | db.session.rollback() 113 | db.session.close() 114 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 115 | 116 | else: 117 | return jsonify({'message': 'Data does not exist.'}) 118 | 119 | else: 120 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 121 | -------------------------------------------------------------------------------- /back/routes/settings.py: -------------------------------------------------------------------------------- 1 | from app import app , jsonify , request , json , db , ma 2 | import shutil , os 3 | 4 | @app.route('/settings/clearlogins/whatsapp', methods=['POST']) 5 | def clear_login_wp(): 6 | user_data = os.path.abspath("./user-data-wp") 7 | print(user_data) 8 | try: 9 | shutil.rmtree(user_data) 10 | return jsonify({'success': 'Login Cleared'}) 11 | except Exception as e: 12 | print(str(e)) 13 | return jsonify({'success': 'Login Cleared'}) 14 | 15 | @app.route('/settings/clearlogins/sms', methods=['POST']) 16 | def clear_login_sms(): 17 | user_data = os.path.abspath("./user-data-sms") 18 | print(user_data) 19 | try: 20 | shutil.rmtree(user_data) 21 | return jsonify({'success': 'Login Cleared'}) 22 | except Exception as e: 23 | print(str(e)) 24 | return jsonify({'success': 'Login Cleared'}) 25 | 26 | -------------------------------------------------------------------------------- /back/routes/tag.py: -------------------------------------------------------------------------------- 1 | from app import app , jsonify , request , json 2 | from app import db 3 | from app import ma 4 | 5 | from model import Tag, TagSchema 6 | 7 | @app.route('/get/tag', methods=['GET']) 8 | def get_tag(): 9 | if request.method == 'GET': 10 | 11 | data_schema = TagSchema(many=True) 12 | data = Tag.query.all() 13 | json_data = data_schema.dump(data) 14 | return jsonify(json_data) 15 | 16 | 17 | @app.route('/add/tag', methods=['POST']) 18 | def add_tag(): 19 | if request.method == 'POST': 20 | payload = request.json 21 | print(payload) 22 | if len(payload['name']) != 0: 23 | 24 | check_data = Tag.query.filter_by(name=payload['name'].lower().strip()) 25 | if check_data.first(): 26 | return jsonify({'message': 'Data - '+check_data.first().name+' already exists.'}) 27 | else: 28 | try: 29 | 30 | new_data = Tag( 31 | payload['name'].lower().strip()) 32 | 33 | db.session.add(new_data) 34 | db.session.commit() 35 | json_data = { 'id' : new_data.id , 'name' : new_data.name} 36 | return jsonify({'success': 'Data Added', 'data' : json_data}) 37 | 38 | except Exception as e: 39 | print(str(e)) 40 | db.session.rollback() 41 | db.session.close() 42 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 43 | else: 44 | return jsonify({'message': 'Empty Data.'}) 45 | 46 | else: 47 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 48 | 49 | 50 | @app.route('/edit/tag', methods=['POST']) 51 | def edit_tag(): 52 | if request.method == 'POST': 53 | 54 | payload = request.json 55 | if payload['name'] is not None: 56 | 57 | check_data = Tag.query.filter_by( 58 | name=payload['name'].lower().strip()).first() 59 | if check_data and check_data.name != payload['name'].lower().strip(): 60 | return jsonify({'message': 'Data - '+check_data.name+' already exists.'}) 61 | else: 62 | try: 63 | new_data = Tag.query.filter_by( 64 | id=payload['id']).first() 65 | new_data.name = payload['name'].lower().strip() 66 | db.session.commit() 67 | return jsonify({'success': 'Data Updated'}) 68 | 69 | except Exception as e: 70 | print(str(e)) 71 | 72 | db.session.rollback() 73 | db.session.close() 74 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 75 | else: 76 | return jsonify({'message': 'Empty Data.'}) 77 | 78 | else: 79 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 80 | 81 | 82 | @app.route('/delete/tag', methods=['POST']) 83 | def delete_tag(): 84 | if request.method == 'POST': 85 | payload = request.json 86 | check_data = Tag.query.filter_by(id=payload['id']) 87 | if check_data.first(): 88 | if len(check_data.first().tag_contact) is int(0): 89 | try: 90 | check_data.delete() 91 | db.session.commit() 92 | return jsonify({'success': 'Data deleted'}) 93 | except Exception as e: 94 | db.session.rollback() 95 | db.session.close() 96 | return jsonify({'message': 'Something unexpected happened. Check logs', 'log': str(e)}) 97 | else: 98 | return jsonify({'message': 'Cannot delete data. Being used by other master.'}) 99 | 100 | else: 101 | return jsonify({'message': 'Data does not exist.'}) 102 | 103 | else: 104 | return jsonify({'message': 'Invalid HTTP method . Use POST instead.'}) 105 | -------------------------------------------------------------------------------- /back/routes/templates.py: -------------------------------------------------------------------------------- 1 | from app import app , jsonify , request , json , UPLOAD_FOLDER , IntegrityError 2 | from app import db 3 | from app import ma 4 | import os , shutil 5 | 6 | from model import Template , TemplateSchema 7 | 8 | @app.route('/get/template', methods=['GET']) 9 | def get_template(): 10 | schema = TemplateSchema(many=True) 11 | data = Template.query.all() 12 | return jsonify(schema.dump(data)) 13 | 14 | @app.route('/add/template', methods=['POST']) 15 | def add_template(): 16 | payload = json.loads(request.form['data']) 17 | file = request.files 18 | if payload : 19 | try: 20 | new_data = Template( 21 | payload['name'], payload['message'] ,payload['filetype'] ) 22 | 23 | if len(file) != 0: 24 | file = request.files['image'] 25 | try: 26 | 27 | if file : 28 | filename =file.filename 29 | foldertemp = os.path.join( 30 | UPLOAD_FOLDER, 'templates') 31 | 32 | if os.path.exists(foldertemp): 33 | filetemp = os.path.join( 34 | foldertemp, filename) 35 | file.save(filetemp) 36 | setattr(new_data, 'path', filetemp) 37 | else: 38 | 39 | os.makedirs(foldertemp) 40 | 41 | filetemp = os.path.join( 42 | foldertemp, filename) 43 | file.save(filetemp) 44 | setattr(new_data, 'path', filetemp) 45 | else: 46 | return jsonify({'message': 'Image file not supported.'}) 47 | 48 | except IntegrityError as e: 49 | print(str(e)) 50 | return jsonify({'message': 'Image file not supported.'}) 51 | 52 | 53 | except Exception as e: 54 | print(str(e)) 55 | 56 | db.session.add(new_data) 57 | db.session.commit() 58 | return jsonify({'success': "Data added"}) 59 | 60 | except Exception as e: 61 | # return Exception for duplicate data 62 | print(str(e)) 63 | return jsonify({'message': "Something went wrong"}) 64 | 65 | 66 | @app.route('/delete/template/', methods=['POST']) 67 | def delete_template(id): 68 | 69 | try: 70 | new_data = Template.query.filter_by(id = int(id)) 71 | if new_data.first(): 72 | if len(new_data.first().template_job) is 0: 73 | 74 | new_data.delete() 75 | db.session.commit() 76 | return jsonify({'success': "Data Deleted"}) 77 | else: 78 | return jsonify({'message': "Cannot delete - in use by a job"}) 79 | 80 | else: 81 | return jsonify({'message': "Data doesn't exists"}) 82 | except Exception as e: 83 | # return Exception for duplicate data 84 | print(str(e)) 85 | -------------------------------------------------------------------------------- /back/run.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | from app import db 3 | 4 | if __name__ == "__main__": 5 | db.create_all() 6 | app.run(port= "5050" , debug = False) -------------------------------------------------------------------------------- /back/scraper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/highoncarbs/automato/5a8a7e8cae613962684c725edf4bf88e324ef775/back/scraper/__init__.py -------------------------------------------------------------------------------- /back/task.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/highoncarbs/automato/5a8a7e8cae613962684c725edf4bf88e324ef775/back/task.db -------------------------------------------------------------------------------- /front/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /front/README.md: -------------------------------------------------------------------------------- 1 | # front 2 | 3 | > Front end for Automato 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | $ npm run install 10 | 11 | # serve with hot reload at localhost:3000 12 | $ npm run dev 13 | 14 | # build for production and launch server 15 | $ npm run build 16 | $ npm run start 17 | 18 | # generate static project 19 | $ npm run generate 20 | ``` 21 | 22 | For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org). 23 | -------------------------------------------------------------------------------- /front/assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). 8 | -------------------------------------------------------------------------------- /front/assets/b-style.scss: -------------------------------------------------------------------------------- 1 | $tabs-link-active-color: black ; 2 | @import "~bulma/sass/utilities/_all"; 3 | @import "~bulma/sass/base/_all"; 4 | @import "~bulma/sass/components/_all"; 5 | @import "~bulma/sass/elements/_all"; 6 | @import "~bulma/sass/form/_all"; 7 | 8 | $speed-slow: 150ms !default; 9 | $speed-slower: 250ms !default; 10 | 11 | @import "~bulma/sass/elements/table.sass"; 12 | @import "~bulma/sass/components/tabs.sass"; 13 | @import "~bulma/sass/components/dropdown.sass"; 14 | @import "~bulma/sass/elements/tag.sass"; 15 | @import "~buefy/src/scss/utils/_all.scss"; 16 | @import "~buefy/src/scss/components/_checkbox.scss"; 17 | @import "~buefy/src/scss/components/_table.scss"; 18 | @import "~buefy/src/scss/components/_tabs.scss"; 19 | @import "~buefy/src/scss/components/_dropdown.scss"; 20 | @import "~buefy/src/scss/components/_notices.scss"; 21 | @import "~buefy/src/scss/components/_tag.scss"; 22 | @import "~buefy/src/scss/components/_taginput.scss"; 23 | @import "~buefy/src/scss/components/_radio.scss"; 24 | @import "~buefy/src/scss/components/_autocomplete.scss"; 25 | -------------------------------------------------------------------------------- /front/assets/main.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/highoncarbs/automato/5a8a7e8cae613962684c725edf4bf88e324ef775/front/assets/main.css -------------------------------------------------------------------------------- /front/components/EditContact.vue: -------------------------------------------------------------------------------- 1 |