commit a0498a3774ecbf9c78eca3a942a2d99438adb7fc Author: awkawb Date: Sun Sep 10 13:04:17 2023 -0400 Initial commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..de614bc --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +IP_ADDR = allpawcare.com +DOMAIN = allpawcare.com +USER = awkawb +IMG_ORIGINAL = "img_original" +IMG_PUBLIC = "all_paw_care/static/img" +LOCAL_ARCHIVE = $(shell pwd)/public.tar +PUBLIC_DIR = "$(shell pwd)/public" +REMOTE_DIR = /home/awkawb + +.PHONY: dev-server +dev-server: + flask run --no-reload + +.PHONY: process-images +process-images: + for + +.PHONY: push-archive +push-archive: archive + scp $(LOCAL_ARCHIVE) root@$(DOMAIN):/srv/www && rm $(LOCAL_ARCHIVE) + +.PHONY: archive +archive: + tar -cf $(LOCAL_ARCHIVE) $(PUBLIC_DIR) + +.PHONY: ssh +ssh: + ssh $(USER)@$(DOMAIN) + +.PHONY: ssh-root +ssh-root: + ssh root@$(DOMAIN) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e0a1a11 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Andrew's animal caregiving website + +## Technologies used + + * python + * flask + * Bootstrap + +## shell.nix + +A nix development environment. Include python3 and flask. To enter +the development environment run `nix-shell`. + +## Development server + +Development server can be run with `make dev-server`. The server runs +locally on port 5000. diff --git a/all_paw_care/__init__.py b/all_paw_care/__init__.py new file mode 100644 index 0000000..317bac1 --- /dev/null +++ b/all_paw_care/__init__.py @@ -0,0 +1,33 @@ +# App route blueprints +from all_paw_care.forms import forms +from all_paw_care.pages import pages +from all_paw_care.users import users +from all_paw_care.db.actions import ensure_tables + +# App config +from config import Config + +# Flask +from flask import Flask + +# SQLAlchemy +from sqlalchemy import create_engine +from sqlalchemy.orm import Session + +def create_app(): + # Initialize app + all_paw_care = Flask(__name__) + + # Import config + all_paw_care.config.from_object(Config) + + # Register app blueprints + all_paw_care.register_blueprint(pages) + all_paw_care.register_blueprint(forms) + all_paw_care.register_blueprint(users) + + # Ensure database tables + ensure_tables() + + return all_paw_care + diff --git a/all_paw_care/db/__init__.py b/all_paw_care/db/__init__.py new file mode 100644 index 0000000..79d761d --- /dev/null +++ b/all_paw_care/db/__init__.py @@ -0,0 +1,11 @@ +from config import Config + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +# Database engine object +DBEngine = create_engine(Config.SQLALCHEMY_DATABASE_URI, echo=True) + +# Database session object +DBSession = sessionmaker(DBEngine) + diff --git a/all_paw_care/db/actions.py b/all_paw_care/db/actions.py new file mode 100644 index 0000000..fb52471 --- /dev/null +++ b/all_paw_care/db/actions.py @@ -0,0 +1,65 @@ +# Local +from all_paw_care.db.types.user import User +from all_paw_care.db.types.base import Base +from all_paw_care.db import DBEngine +from all_paw_care.db import DBSession + +from sqlalchemy.orm import Session +from sqlalchemy import select + +def ensure_tables(): + Base.metadata.create_all(DBEngine) + +def login(username: str): + if get_user(username): + return True + + else: + return False + +def add_user(username: str): + with DBSession() as session, session.begin(): + try: + session.add(User(username=username)) + + except: + session.rollback() + + finally: + session.commit() + + return True + +def get_users(): + with DBSession() as session, session.begin(): + users = list() + database_users = session.scalars(select(User).order_by(User.id)).all() + for database_user in database_users: + user = (database_user.id, database_user.username) + users.append(user) + + return users + +def get_user(username: str = None): + if username: + with DBSession() as session, session.begin(): + user = session.scalars( + select(User).where(User.username == username)).all() + + if len(user) == 1: + return (user[0].id, user[0].username) + + elif len(user) == 0: + return None + + else: + return None + + + +def user_exists(username: str): + with DBSession() as session, session.begin(): + users = session.execute( + select(User).where(User.username == username)) + print(users) + diff --git a/all_paw_care/db/metadata.py b/all_paw_care/db/metadata.py new file mode 100644 index 0000000..8615824 --- /dev/null +++ b/all_paw_care/db/metadata.py @@ -0,0 +1,4 @@ +from sqlalchemy.schema import MetaData + +DBMetadata = MetaData(schema='all_paw_care') + diff --git a/all_paw_care/db/types/base.py b/all_paw_care/db/types/base.py new file mode 100644 index 0000000..d4c131f --- /dev/null +++ b/all_paw_care/db/types/base.py @@ -0,0 +1,5 @@ +from sqlalchemy.orm import DeclarativeBase + +class Base(DeclarativeBase): + pass + diff --git a/all_paw_care/db/types/user.py b/all_paw_care/db/types/user.py new file mode 100644 index 0000000..37bf73b --- /dev/null +++ b/all_paw_care/db/types/user.py @@ -0,0 +1,13 @@ +from all_paw_care.db.types.base import Base + +from sqlalchemy import String +from sqlalchemy.orm import Mapped +from sqlalchemy.orm import mapped_column + +class User(Base): + __tablename__ = 'users' + + id: Mapped[int] = mapped_column(primary_key=True) + username: Mapped[str] = mapped_column( + String(40), nullable=False, unique=True) + diff --git a/all_paw_care/forms/README.md b/all_paw_care/forms/README.md new file mode 100644 index 0000000..b2c1554 --- /dev/null +++ b/all_paw_care/forms/README.md @@ -0,0 +1,9 @@ +# Pages + +Blueprints for /forms routes + +## Routes + +- Meet and Greet + + diff --git a/all_paw_care/forms/__init__.py b/all_paw_care/forms/__init__.py new file mode 100644 index 0000000..51f62c2 --- /dev/null +++ b/all_paw_care/forms/__init__.py @@ -0,0 +1,7 @@ +from flask import Blueprint + +forms = Blueprint('forms', __name__, url_prefix='/forms') + +# place here, else circular import errors +from all_paw_care.forms import routes + diff --git a/all_paw_care/forms/routes.py b/all_paw_care/forms/routes.py new file mode 100644 index 0000000..2b27e5a --- /dev/null +++ b/all_paw_care/forms/routes.py @@ -0,0 +1,17 @@ +from all_paw_care.send_mail import SendMail +from all_paw_care.forms import forms + +from flask import render_template,request + +@forms.route('/meet_and_greet', methods=['GET', 'POST']) +def meet_and_greet(): + if request.method == 'POST': + send_client = SendMail() + send_client.send_meet_and_greet_request(request.form['client-name'], + request.form['client-pets'],request.form['visit-type'], + request.form['meet-greet-date'],request.form['meet-greet-time'], + request.form['contact-type'],request.form['contact'], + request.form['notes']) + + return render_template("forms/meet-and-greet.html") + diff --git a/all_paw_care/invoice_ninja/__init__.py b/all_paw_care/invoice_ninja/__init__.py new file mode 100644 index 0000000..1a7524a --- /dev/null +++ b/all_paw_care/invoice_ninja/__init__.py @@ -0,0 +1,25 @@ +# Python Invoice Ninja library +#from all_paw_care.invoice_ninja import swagger_client +#from all_paw_care.swagger_client.rest import ApiException + +# Invoice Ninja library dependencies +#from __future__ import print_function +#import time +#from pprint import pprint + +BASE_URL='https://invoice.allpawcare.com/api/v1' + +API_TOKEN='r4AKEz9xRgk2SA7IotAvLrj64f7s0BczLDVjVwmjiPWeyG0fpu2eyib4VKI23QNO' + +INVOICE_NINJA_USERNAME = 'billing@allpawcare.com' +INVOICE_NINJA_PASSWORD = """sw'1eMqN6#9fO!3"RY$L""" + +INVOICE_NINJA_LOGIN_DICT = { + 'email': INVOICE_NINJA_USERNAME, + 'pasword': INVOICE_NINJA_PASSWORD + } + +#invoice_ninja = swagger_client() + + + diff --git a/all_paw_care/invoice_ninja/endpoints/__init__.py b/all_paw_care/invoice_ninja/endpoints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/all_paw_care/invoice_ninja/endpoints/clients/__init__.py b/all_paw_care/invoice_ninja/endpoints/clients/__init__.py new file mode 100644 index 0000000..8799c30 --- /dev/null +++ b/all_paw_care/invoice_ninja/endpoints/clients/__init__.py @@ -0,0 +1,94 @@ +from all_paw_care.invoice_ninja import API_TOKEN +from all_paw_care.invoice_ninja import BASE_URL +from all_paw_care.invoice_ninja.mappings.client import Client + +import requests + +class Clients(object): + CLIENTS_URL = '{}/clients'.format(BASE_URL) + HEADERS = {'X-API-TOKEN': API_TOKEN} + + def __build_sort_params(self, sort: dict): + sort_params = {'sort': str()} + is_first_entry = True + for option in sort.keys(): + if is_first_entry: + sort_params['sort'] += '{}|{}'.format(option, sort[option]) + is_first_entry = False + + else: + sort_params['sort'] += ' {}|{}'.format(option, sort[option]) + + return sort_params + + def __client_from_dict(self, client: dict): + return Client(client_id=client['id'], + name=client['name'], + address=client['address1'], + city=client['city'], + state=client['state'], + postal_code=client['postal_code'], + phone=client['phone'], + email=client['contacts'][0]['email'], + pets=client['custom_value1']) + + def __clients_from_response(self, response: requests.Response): + clients = list() + for client_dict in response.json()['data']: + clients.append(self.__client_from_dict(client_dict)) + + return clients + + def list_client(self, client_id: str = None): + """ + Get client based on client id. + """ + + if client_id: + request_url = '{}/{}'.format(self.CLIENTS_URL, client_id) + response = requests.get(url=request_url, + headers=self.HEADERS) + + if response.ok: + return self.__client_from_dict(response.json()['data']) + + return None + + def list_clients(self, include: str = 'activities', + sort: dict = dict(), status: str = 'active', + name: str = None): + """ + Get list of clients. + """ + + request_params = dict() + + # Add sort parameters to request + if len(sort) > 0: + request_params.update(self.__build_sort_params(sort)) + + # Add include parameters to request + request_params.update({'include': include}) + + # Add status parameters to request + request_params.update({'status': status}) + + # Add name parameters to request + if name: + request_params.update({'name': name}) + + # Check is request should be sent with parameters + if len(request_params) > 0: + response = requests.get(url=self.CLIENTS_URL, + params=request_params, + headers=self.HEADERS) + else: + response = requests.get(url=self.CLIENTS_URL, + headers=self.HEADERS) + + if response.ok: + return self.__clients_from_response(response) + + else: + return None + diff --git a/all_paw_care/invoice_ninja/endpoints/clients/options/__init__.py b/all_paw_care/invoice_ninja/endpoints/clients/options/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/all_paw_care/invoice_ninja/endpoints/clients/options/list_clients/__init__.py b/all_paw_care/invoice_ninja/endpoints/clients/options/list_clients/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/all_paw_care/invoice_ninja/endpoints/clients/options/list_clients/sort.py b/all_paw_care/invoice_ninja/endpoints/clients/options/list_clients/sort.py new file mode 100644 index 0000000..ce15840 --- /dev/null +++ b/all_paw_care/invoice_ninja/endpoints/clients/options/list_clients/sort.py @@ -0,0 +1,15 @@ +class Sort(object): + def __init__(self, id: str = None, + name: str = None, + balance: str = None): + self.sort_params = {'sort': str()} + is_first_entry = True + if id: + if is_first_entry: + self.sort_params['sort'] += '{}|{}'.format(option, sort[option]) + is_first_entry = False + + else: + self.sort_params['sort'] += ' {}|{}'.format(option, sort[option]) + + diff --git a/all_paw_care/invoice_ninja/mappings/__init__.py b/all_paw_care/invoice_ninja/mappings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/all_paw_care/invoice_ninja/mappings/client.py b/all_paw_care/invoice_ninja/mappings/client.py new file mode 100644 index 0000000..bdb5888 --- /dev/null +++ b/all_paw_care/invoice_ninja/mappings/client.py @@ -0,0 +1,54 @@ + +class Client(object): + + def __init__(self, client_id: int = None, name: str = None, + address: str = None, city: str = None, state: str = None, + postal_code: str = None, phone: str = None, + email: str = None, pets: str = None): + self.id = client_id + self.name = name + self.address = address + self.city = city + self.state = state + self.postal_code = postal_code + self.phone = phone + self.email = email + self.pets = pets + + def __str__(self): + return 'Client({}, {}, {}, {}, {}, {}, {}, {}, {})'.format( + self.id, + self.name, + self.address, + self.city, + self.state, + self.postal_code, + self.phone, + self.email, + self.pets + ) + + def get_name(self): + return self.name + + def get_id(self): + return self.id + + def get_address(self): + return self.address + + def get_city(self): + return self.city + + def get_state(self): + return self.state + + def get_postal_code(self): + return self.postal_code + + def get_phone(self): + return self.postal_code + + def get_email(self): + return self.email + diff --git a/all_paw_care/pages/README.md b/all_paw_care/pages/README.md new file mode 100644 index 0000000..c3ac9ae --- /dev/null +++ b/all_paw_care/pages/README.md @@ -0,0 +1,11 @@ +# Pages + +Blueprints for /pages routes + +## Routes + +- Home +- FAQ +- Services + + diff --git a/all_paw_care/pages/__init__.py b/all_paw_care/pages/__init__.py new file mode 100644 index 0000000..b3ecf27 --- /dev/null +++ b/all_paw_care/pages/__init__.py @@ -0,0 +1,7 @@ +from flask import Blueprint + +pages = Blueprint('pages', __name__, url_prefix='/pages') + +# place here, else circular import errors +from all_paw_care.pages import routes + diff --git a/all_paw_care/pages/routes.py b/all_paw_care/pages/routes.py new file mode 100644 index 0000000..b5f14e9 --- /dev/null +++ b/all_paw_care/pages/routes.py @@ -0,0 +1,17 @@ +from all_paw_care.pages import pages + +from flask import render_template,url_for + +@pages.route('/') +@pages.route('/home') +def home(): + return render_template("pages/index.html") + +@pages.route('/faq') +def faq(): + return render_template("pages/faq.html") + +@pages.route('/services') +def services(): + return render_template("pages/services.html") + diff --git a/all_paw_care/send_mail.py b/all_paw_care/send_mail.py new file mode 100644 index 0000000..b2bd301 --- /dev/null +++ b/all_paw_care/send_mail.py @@ -0,0 +1,100 @@ +from config import Config + +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from smtplib import SMTP_SSL +import ssl + +class SendMail(object): + def __init__(self): + # SMTP credentials + self.__server = Config.MAIL_SERVER + self.__port = Config.MAIL_PORT + self.__user = Config.MAIL_USER + self.__password = Config.MAIL_PASSWORD + self.__default_sender = Config.MAIL_SENDER + self.__default_recipients = Config.MAIL_RECIPIENTS + + # SSL context + self.__default_ssl_context = ssl.create_default_context() + + # Setup SMTP client + self.__smtp_client = SMTP_SSL(host=self.__server,port=self.__port,) + self.__smtp_client.login(self.__user,self.__password) + + # Set default message + self.set_message_subject() + self.set_message() + + def set_message(self, msg: str = '

Test email message.

'): + self.__message_root = MIMEMultipart() + self.__message_alt = MIMEMultipart('alternative') + + self.__message_root['Subject'] = self.__message_subject + self.__message_root['From'] = self.__default_sender + self.__message_root['To'] = ', '.join(self.__default_recipients) + + self.__message_root.attach(self.__message_alt) + self.__message_alt.attach(MIMEText(msg,'html')) + + return True + + def set_message_subject(self, subject: str = 'allpawcare.com test email'): + self.__message_subject = subject + + return True + + def send(self): + self.__smtp_client.sendmail(self.__default_sender,self.__default_recipients, + self.__message_root.as_string()) + + return True + + def send_meet_and_greet_request(self, client_name: str = 'Andrew', + client_pets: str = 'Nabooru', visit_type: str = 'Test', + meeting_date: str = 'Test', + meeting_time: str = '10:00', + contact_type: str = 'Test', + client_contact: str = 'Test', + client_notes: str = 'This is just a test.'): + self.set_message_subject('New meet and greet request') + self.set_message(''' + +

A new meet and greet request

+ +

+ {name} has requested a meet and greet. +

+ + + + '''.format(name = client_name, + pets = client_pets, time = meeting_time, + date = meeting_date, vtype = visit_type, + ctype = contact_type, contact = client_contact, + notes = client_notes)) + + self.send() + + return True + diff --git a/all_paw_care/static/img/about_me/about_me_1_lg.jpg b/all_paw_care/static/img/about_me/about_me_1_lg.jpg new file mode 100644 index 0000000..3d09eda Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_1_lg.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_1_md.jpg b/all_paw_care/static/img/about_me/about_me_1_md.jpg new file mode 100644 index 0000000..ae9bf4a Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_1_md.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_1_sm.jpg b/all_paw_care/static/img/about_me/about_me_1_sm.jpg new file mode 100644 index 0000000..f1aaf68 Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_1_sm.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_1_xl.jpg b/all_paw_care/static/img/about_me/about_me_1_xl.jpg new file mode 100644 index 0000000..73f91a4 Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_1_xl.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_1_xxl.jpg b/all_paw_care/static/img/about_me/about_me_1_xxl.jpg new file mode 100644 index 0000000..3ec6128 Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_1_xxl.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_2_lg.jpg b/all_paw_care/static/img/about_me/about_me_2_lg.jpg new file mode 100644 index 0000000..4582270 Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_2_lg.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_2_md.jpg b/all_paw_care/static/img/about_me/about_me_2_md.jpg new file mode 100644 index 0000000..9818a2a Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_2_md.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_2_sm.jpg b/all_paw_care/static/img/about_me/about_me_2_sm.jpg new file mode 100644 index 0000000..f5102c8 Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_2_sm.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_2_xl.jpg b/all_paw_care/static/img/about_me/about_me_2_xl.jpg new file mode 100644 index 0000000..8a6c842 Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_2_xl.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_2_xxl.jpg b/all_paw_care/static/img/about_me/about_me_2_xxl.jpg new file mode 100644 index 0000000..ab4b846 Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_2_xxl.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_3_lg.jpg b/all_paw_care/static/img/about_me/about_me_3_lg.jpg new file mode 100644 index 0000000..1e624a6 Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_3_lg.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_3_md.jpg b/all_paw_care/static/img/about_me/about_me_3_md.jpg new file mode 100644 index 0000000..ca346b9 Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_3_md.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_3_sm.jpg b/all_paw_care/static/img/about_me/about_me_3_sm.jpg new file mode 100644 index 0000000..fb626aa Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_3_sm.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_3_xl.jpg b/all_paw_care/static/img/about_me/about_me_3_xl.jpg new file mode 100644 index 0000000..da61a3e Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_3_xl.jpg differ diff --git a/all_paw_care/static/img/about_me/about_me_3_xxl.jpg b/all_paw_care/static/img/about_me/about_me_3_xxl.jpg new file mode 100644 index 0000000..bdbfc8a Binary files /dev/null and b/all_paw_care/static/img/about_me/about_me_3_xxl.jpg differ diff --git a/all_paw_care/static/img/services/dog_walking_card_lg.png b/all_paw_care/static/img/services/dog_walking_card_lg.png new file mode 100644 index 0000000..55cc325 Binary files /dev/null and b/all_paw_care/static/img/services/dog_walking_card_lg.png differ diff --git a/all_paw_care/static/img/services/dog_walking_card_md.png b/all_paw_care/static/img/services/dog_walking_card_md.png new file mode 100644 index 0000000..79c77be Binary files /dev/null and b/all_paw_care/static/img/services/dog_walking_card_md.png differ diff --git a/all_paw_care/static/img/services/dog_walking_card_sm.png b/all_paw_care/static/img/services/dog_walking_card_sm.png new file mode 100644 index 0000000..87c61ce Binary files /dev/null and b/all_paw_care/static/img/services/dog_walking_card_sm.png differ diff --git a/all_paw_care/static/img/services/dog_walking_card_xl.png b/all_paw_care/static/img/services/dog_walking_card_xl.png new file mode 100644 index 0000000..c809bed Binary files /dev/null and b/all_paw_care/static/img/services/dog_walking_card_xl.png differ diff --git a/all_paw_care/static/img/services/dog_walking_card_xxl.png b/all_paw_care/static/img/services/dog_walking_card_xxl.png new file mode 100644 index 0000000..f8978ab Binary files /dev/null and b/all_paw_care/static/img/services/dog_walking_card_xxl.png differ diff --git a/all_paw_care/static/img/services/drop_in_card_lg.png b/all_paw_care/static/img/services/drop_in_card_lg.png new file mode 100644 index 0000000..be1620b Binary files /dev/null and b/all_paw_care/static/img/services/drop_in_card_lg.png differ diff --git a/all_paw_care/static/img/services/drop_in_card_md.png b/all_paw_care/static/img/services/drop_in_card_md.png new file mode 100644 index 0000000..18c4212 Binary files /dev/null and b/all_paw_care/static/img/services/drop_in_card_md.png differ diff --git a/all_paw_care/static/img/services/drop_in_card_sm.png b/all_paw_care/static/img/services/drop_in_card_sm.png new file mode 100644 index 0000000..2fa30c5 Binary files /dev/null and b/all_paw_care/static/img/services/drop_in_card_sm.png differ diff --git a/all_paw_care/static/img/services/drop_in_card_xl.png b/all_paw_care/static/img/services/drop_in_card_xl.png new file mode 100644 index 0000000..c3735fe Binary files /dev/null and b/all_paw_care/static/img/services/drop_in_card_xl.png differ diff --git a/all_paw_care/static/img/services/drop_in_card_xxl.png b/all_paw_care/static/img/services/drop_in_card_xxl.png new file mode 100644 index 0000000..727153c Binary files /dev/null and b/all_paw_care/static/img/services/drop_in_card_xxl.png differ diff --git a/all_paw_care/static/img/services/house_sitting_card_lg.png b/all_paw_care/static/img/services/house_sitting_card_lg.png new file mode 100644 index 0000000..8b83006 Binary files /dev/null and b/all_paw_care/static/img/services/house_sitting_card_lg.png differ diff --git a/all_paw_care/static/img/services/house_sitting_card_md.png b/all_paw_care/static/img/services/house_sitting_card_md.png new file mode 100644 index 0000000..97d0041 Binary files /dev/null and b/all_paw_care/static/img/services/house_sitting_card_md.png differ diff --git a/all_paw_care/static/img/services/house_sitting_card_sm.png b/all_paw_care/static/img/services/house_sitting_card_sm.png new file mode 100644 index 0000000..49fa769 Binary files /dev/null and b/all_paw_care/static/img/services/house_sitting_card_sm.png differ diff --git a/all_paw_care/static/img/services/house_sitting_card_xl.png b/all_paw_care/static/img/services/house_sitting_card_xl.png new file mode 100644 index 0000000..efc3f49 Binary files /dev/null and b/all_paw_care/static/img/services/house_sitting_card_xl.png differ diff --git a/all_paw_care/static/img/services/house_sitting_card_xxl.png b/all_paw_care/static/img/services/house_sitting_card_xxl.png new file mode 100644 index 0000000..6ae570e Binary files /dev/null and b/all_paw_care/static/img/services/house_sitting_card_xxl.png differ diff --git a/all_paw_care/templates/forms/meet-and-greet.html b/all_paw_care/templates/forms/meet-and-greet.html new file mode 100644 index 0000000..bda2d7b --- /dev/null +++ b/all_paw_care/templates/forms/meet-and-greet.html @@ -0,0 +1,102 @@ +{% extends "jinja/types/form.html" %} + +{% set title %}Request meet and greet{% endset %} + +{% block content %} +
+

Meet & greet

+ +
+
+ Pet name(s) + +
+ +
+ Name + +
+ +
+ +
+ Visit type + + + + + + + + + +
+ +
+
+
+ Date + +
+
+ +
+
+ Time + +
+
+
+ +
+
+ Contact + + + + + + + + +
+
+ +
+ Additional notes + +
+ +
+ +
+
+{% endblock %} + diff --git a/all_paw_care/templates/jinja/footer.html b/all_paw_care/templates/jinja/footer.html new file mode 100644 index 0000000..878f6fd --- /dev/null +++ b/all_paw_care/templates/jinja/footer.html @@ -0,0 +1,10 @@ + + diff --git a/all_paw_care/templates/jinja/header.html b/all_paw_care/templates/jinja/header.html new file mode 100644 index 0000000..97918fc --- /dev/null +++ b/all_paw_care/templates/jinja/header.html @@ -0,0 +1,8 @@ +
+ +
+ diff --git a/all_paw_care/templates/jinja/menu.html b/all_paw_care/templates/jinja/menu.html new file mode 100644 index 0000000..f1b4e7a --- /dev/null +++ b/all_paw_care/templates/jinja/menu.html @@ -0,0 +1,32 @@ + + diff --git a/all_paw_care/templates/jinja/types/form.html b/all_paw_care/templates/jinja/types/form.html new file mode 100644 index 0000000..e540578 --- /dev/null +++ b/all_paw_care/templates/jinja/types/form.html @@ -0,0 +1,29 @@ + + + + + + + {% block title %}{% endblock %} + + + + + + {% include "jinja/header.html" %} + +
+
+
+ {% block content %} + {% endblock %} +
+
+
+ + {% include "jinja/footer.html" %} + + + + + diff --git a/all_paw_care/templates/jinja/types/page.html b/all_paw_care/templates/jinja/types/page.html new file mode 100644 index 0000000..4dd1f9d --- /dev/null +++ b/all_paw_care/templates/jinja/types/page.html @@ -0,0 +1,27 @@ + + + + + + {{ title }} + + + + + {% include "jinja/header.html" %} + +
+ + +
+ {% block content %} + {% endblock %} +
+
+ + {% include "jinja/footer.html" %} + + + + + diff --git a/all_paw_care/templates/pages/faq.html b/all_paw_care/templates/pages/faq.html new file mode 100644 index 0000000..1e38b81 --- /dev/null +++ b/all_paw_care/templates/pages/faq.html @@ -0,0 +1,36 @@ +{% extends "jinja/types/page.html" %} + +{% set title %}FAQ{% endset %} + +{% block content %} +
+

+ Frequently asked questions +

+
+ + {% for faq in config.FAQS.items() %} +
+
+
+ + + +
+
+ {{ faq[1][1] }} +
+
+ +
+
+
+ + + {% endfor %} +{% endblock %} + diff --git a/all_paw_care/templates/pages/index.html b/all_paw_care/templates/pages/index.html new file mode 100644 index 0000000..b693c69 --- /dev/null +++ b/all_paw_care/templates/pages/index.html @@ -0,0 +1,37 @@ +{% extends "jinja/types/page.html" %} + +{% set title %}About me{% endset %} + +{% block content %} +
+

+ About me +

+
+ + {% for row in config.ABOUT_ME %} +
+
+

+ {{ row[1] }} +

+
+ +
+ + {% for img in row[0] %} + {% if not loop.last %} + + {% else %} + + {% endif %} + {% endfor %} + + + +
+
+ {% endfor %} +{% endblock %} diff --git a/all_paw_care/templates/pages/services.html b/all_paw_care/templates/pages/services.html new file mode 100644 index 0000000..03ff11e --- /dev/null +++ b/all_paw_care/templates/pages/services.html @@ -0,0 +1,121 @@ +{% extends "jinja/types/page.html" %} + +{% set title %}Services{% endset %} + +{% block content %} +
+

+ Services & Pricing +

+
+ +
+

+ Primary services: +

+
+ +
+ {% for service in config.SERVICES.items() %} +
+
+ + {% for img in service[1][0] %} + {% if not loop.last %} + + {% else %} + + {% endif %} + {% endfor %} + + + +
+

{{ service[0] }}

+ +
+ +

+ {{ service[1][1] }} +

+ + + +

+ {{ service[1][2] }} + +

    + {% for included in service[1][3] %} +
  • {{ included }}
  • + {% endfor %} +
+

+
+
+
+ {% endfor %} +
+ +
+

+ Add-on services: +

+
+ +
+ {% for service in config.SERVICES.items() %} +
+
+ + {% for img in service[1][0] %} + {% if not loop.last %} + + {% else %} + + {% endif %} + {% endfor %} + + Woman walking dog + +
+

{{ service[0] }}

+ +
+ +

+ {{ service[1][1] }} +

+ + + +

+ {{ service[1][2] }} + +

    + {% for included in service[1][3] %} +
  • {{ included }}
  • + {% endfor %} +
+

+
+
+
+ {% endfor %} +
+ +
+ +
+{% endblock %} + diff --git a/all_paw_care/templates/user/user_create.html b/all_paw_care/templates/user/user_create.html new file mode 100644 index 0000000..fcbe80b --- /dev/null +++ b/all_paw_care/templates/user/user_create.html @@ -0,0 +1,29 @@ +{% extends "jinja/types/form.html" %} + +{% set title %}Create user account{% endset %} + +{% block content %} +
+
+

+ Create user account +

+
+ +
+
+ Username + +
+
+ +
+ +
+
+{% endblock %} + diff --git a/all_paw_care/templates/user/user_login.html b/all_paw_care/templates/user/user_login.html new file mode 100644 index 0000000..43bb99d --- /dev/null +++ b/all_paw_care/templates/user/user_login.html @@ -0,0 +1,29 @@ +{% extends "jinja/types/form.html" %} + +{% set title %}Login{% endset %} + +{% block content %} +
+
+

+ User login +

+
+ +
+
+ Username + +
+
+ +
+ +
+
+{% endblock %} + diff --git a/all_paw_care/users/README.md b/all_paw_care/users/README.md new file mode 100644 index 0000000..d65177a --- /dev/null +++ b/all_paw_care/users/README.md @@ -0,0 +1,9 @@ +# Users + +Blueprints for /users routes + +## Routes + +- /login +- / + diff --git a/all_paw_care/users/__init__.py b/all_paw_care/users/__init__.py new file mode 100644 index 0000000..56a14c2 --- /dev/null +++ b/all_paw_care/users/__init__.py @@ -0,0 +1,7 @@ +from flask import Blueprint + +users = Blueprint('users', __name__, url_prefix='/users') + +# place here, else circular import errors +from all_paw_care.users import routes + diff --git a/all_paw_care/users/routes.py b/all_paw_care/users/routes.py new file mode 100644 index 0000000..7d59103 --- /dev/null +++ b/all_paw_care/users/routes.py @@ -0,0 +1,39 @@ +# Blueprint import +from all_paw_care.users import users + +# TODO uncomment database imports +# Database imports +from all_paw_care.db.actions import add_user +from all_paw_care.db.actions import login +from all_paw_care.db.types.user import User + +# Flask imports +from flask import render_template +from flask import request + +# SQLAlchemy imports +from sqlalchemy import select + +@users.route('/login', methods=['GET', 'POST']) +def user_login(): + if request.method == 'POST': + if login(request.form['username']): + return 'Your now logged in.' + + else: + return 'User not found.' + + return render_template("user/user_login.html") + +@users.route('/create', methods=['GET', 'POST']) +def user_create(): + if request.method == 'POST': + try: + add_user(request.form['username']) + return 'User created sucessfully.' + + except: + return 'User exists' + + return render_template("user/user_create.html") + diff --git a/config.py b/config.py new file mode 100644 index 0000000..7a9dd8e --- /dev/null +++ b/config.py @@ -0,0 +1,150 @@ +import os + +class Config(object): + # App base directory + BASE_DIR = os.path.dirname(os.path.realpath(__file__)) + + # Mail client config + MAIL_SERVER = 'smtp.fastmail.com' + MAIL_PORT = 465 + MAIL_USER = 'awkawb@awkawb.cloud' + MAIL_PASSWORD = 'm3vaxbmp3tx9fqx4' + MAIL_SENDER = 'support@allpawcare.com' + MAIL_RECIPIENTS = ['support@allpawcare.com'] + + # Database config + DB_FILE = 'all_paw_care.db' + SQLALCHEMY_DATABASE_URI = 'sqlite:///' + BASE_DIR + '/' + DB_FILE + + # Image breakpoints + IMG_BREAKPOINTS = [ + '576px', + '768px', + '992px', + '1200px', + '1400px' + ] + + # About me page content + ABOUT_ME = [ + ( + [ + '../static/img/about_me/about_me_1_sm.jpg', + '../static/img/about_me/about_me_1_md.jpg', + '../static/img/about_me/about_me_1_lg.jpg', + '../static/img/about_me/about_me_1_xl.jpg', + '../static/img/about_me/about_me_1_xxl.jpg' + ], + ''' + Before I provided animal caretaking services, I focused + my time on computers. I designed and coded this website! + ''' + ), + ( + [ + '../static/img/about_me/about_me_2_sm.jpg', + '../static/img/about_me/about_me_2_md.jpg', + '../static/img/about_me/about_me_2_lg.jpg', + '../static/img/about_me/about_me_2_xl.jpg', + '../static/img/about_me/about_me_2_xxl.jpg' + ], + ''' + My passion for animals and plants are what I spend + most my time on currently. I love providing quality + animal care. + ''' + ), + ( + [ + '../static/img/about_me/about_me_3_sm.jpg', + '../static/img/about_me/about_me_3_md.jpg', + '../static/img/about_me/about_me_3_lg.jpg', + '../static/img/about_me/about_me_3_xl.jpg', + '../static/img/about_me/about_me_3_xxl.jpg' + ], + ''' + For the past 5 years, my interest in plants and + horticulture have expanded exponentially. As a kid, + my mother always expressed interest in plants. Plants + are something, for me, that took patients and I + had little patients as a child. + ''' + ) + ] + + # Service page content + SERVICES = { + 'Walking': [ + [ + '../static/img/services/dog_walking_card_sm.png', + '../static/img/services/dog_walking_card_md.png', + '../static/img/services/dog_walking_card_lg.png', + '../static/img/services/dog_walking_card_xl.png', + '../static/img/services/dog_walking_card_xxl.png' + ], + '$20/visit', + 'Service includes:', + [ + '30 minute walk.', + 'Bags for poop clean-up.' + ] + ], + 'Drop-in': [ + [ + '../static/img/services/drop_in_card_sm.png', + '../static/img/services/drop_in_card_md.png', + '../static/img/services/drop_in_card_lg.png', + '../static/img/services/drop_in_card_xl.png', + '../static/img/services/drop_in_card_xxl.png' + ], + '$20/visit', + 'Service includes:', + [ + '30 minute in home visit.', + 'Bags for poop clean-up.' + ] + ], + 'House sitting': [ + [ + '../static/img/services/house_sitting_card_sm.png', + '../static/img/services/house_sitting_card_md.png', + '../static/img/services/house_sitting_card_lg.png', + '../static/img/services/house_sitting_card_xl.png', + '../static/img/services/house_sitting_card_xxl.png' + ], + '$38/visit', + 'Service includes:', + [ + 'Animal care.', + 'In-home overnight stays.' + ] + ] + } + + # FAQ page content + FAQS = { + 'What kind of animals do you caretake?': ( + 'animal-types-faq', + 'I have experience careing for many types of animals. ' + + 'I provide care for reptiles, mammals, etc.' + ), + 'Can you care for special needs pets?': ( + 'special-needs-faq', + '''I have experience care for special needs, including older + animals''' + ), + 'Can you stay at my house?': ( + 'house-sitting-faq', + 'I offer house sitting services. This includes in home stays.' + ), + 'Can you take care of my plants?': ( + 'plant-faq', + 'I love plants and have a garden of my own. I\'ll gladly ' + + 'remove the mess for you.' + ), + 'My yard is a mess, can you clean up the dog poop?': ( + 'poop-scoop-faq', + 'I do offer animal poop clean up add-on yard' + ) + } + diff --git a/img_original/about_me/about_me_1.jpg b/img_original/about_me/about_me_1.jpg new file mode 100644 index 0000000..e79417c Binary files /dev/null and b/img_original/about_me/about_me_1.jpg differ diff --git a/img_original/about_me/about_me_2.jpg b/img_original/about_me/about_me_2.jpg new file mode 100644 index 0000000..2ce2105 Binary files /dev/null and b/img_original/about_me/about_me_2.jpg differ diff --git a/img_original/about_me/about_me_3.jpg b/img_original/about_me/about_me_3.jpg new file mode 100644 index 0000000..6e25f29 Binary files /dev/null and b/img_original/about_me/about_me_3.jpg differ diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..75ea5d1 --- /dev/null +++ b/shell.nix @@ -0,0 +1,28 @@ +{ pkgs ? import {} }: + + +with pkgs; +pkgs.mkShell { + nativeBuildInputs = [ + # Python development environment + (python3.withPackages(ps: with ps; [ + flask + markdown + requests + sqlalchemy + certifi + six + python-dateutil + urllib3 + ])) + + imagemagick + sqlite + ]; + + shellHook = '' + export FLASK_APP=all_paw_care + export FLASK_DEBUG=1 + ''; +} +