├── .dockerignore ├── .env ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── apps ├── __init__.py ├── authentication │ ├── __init__.py │ ├── forms.py │ ├── models.py │ ├── routes.py │ └── util.py ├── config.py ├── home │ ├── __init__.py │ └── routes.py ├── static │ ├── assets │ │ ├── .gitkeep │ │ ├── css │ │ │ ├── nucleo-icons.css │ │ │ ├── nucleo-svg.css │ │ │ ├── soft-design-system.css │ │ │ ├── soft-design-system.css.map │ │ │ └── soft-design-system.min.css │ │ ├── fonts │ │ │ ├── nucleo-icons.eot │ │ │ ├── nucleo-icons.svg │ │ │ ├── nucleo-icons.ttf │ │ │ ├── nucleo-icons.woff │ │ │ ├── nucleo-icons.woff2 │ │ │ ├── nucleo.eot │ │ │ ├── nucleo.ttf │ │ │ ├── nucleo.woff │ │ │ └── nucleo.woff2 │ │ ├── gulpfile.js │ │ ├── img │ │ │ ├── anastasia.jpg │ │ │ ├── annie-spratt.jpg │ │ │ ├── apple-icon.png │ │ │ ├── blog7-1.jpg │ │ │ ├── blog7-2.jpg │ │ │ ├── blog7-3.jpg │ │ │ ├── brooke.jpg │ │ │ ├── bruce-mars.jpg │ │ │ ├── card-2.jpg │ │ │ ├── card-3.jpg │ │ │ ├── charles.jpg │ │ │ ├── curved-images │ │ │ │ ├── curved-10.jpg │ │ │ │ ├── curved-11.jpg │ │ │ │ ├── curved-6.jpg │ │ │ │ ├── curved-8.jpg │ │ │ │ ├── curved.jpg │ │ │ │ ├── curved1.jpg │ │ │ │ ├── curved11-small.jpg │ │ │ │ ├── curved11.jpg │ │ │ │ ├── curved13.jpg │ │ │ │ ├── curved14.jpg │ │ │ │ ├── curved2.jpg │ │ │ │ ├── curved5-small.jpg │ │ │ │ ├── curved5.jpg │ │ │ │ ├── curved6-small.jpg │ │ │ │ ├── curved6.jpg │ │ │ │ └── curved8.jpg │ │ │ ├── down-arrow-dark.svg │ │ │ ├── down-arrow-white.svg │ │ │ ├── down-arrow.svg │ │ │ ├── favicon.png │ │ │ ├── illustrations │ │ │ │ ├── chat.png │ │ │ │ └── sign-up.png │ │ │ ├── ivana-square.jpg │ │ │ ├── ivana.jpg │ │ │ ├── ivancik.jpg │ │ │ ├── jordan.jpg │ │ │ ├── kal-visuals-square.jpg │ │ │ ├── kal-visuals.jpg │ │ │ ├── logos │ │ │ │ ├── gray-logos │ │ │ │ │ ├── logo-apple.svg │ │ │ │ │ ├── logo-behance.svg │ │ │ │ │ ├── logo-coinbase.svg │ │ │ │ │ ├── logo-digitalocean.svg │ │ │ │ │ ├── logo-facebook.svg │ │ │ │ │ ├── logo-nasa.svg │ │ │ │ │ ├── logo-netflix.svg │ │ │ │ │ ├── logo-pinterest.svg │ │ │ │ │ ├── logo-spotify.svg │ │ │ │ │ └── logo-vodafone.svg │ │ │ │ ├── medium-logos │ │ │ │ │ ├── logo-apple.svg │ │ │ │ │ ├── logo-behance.svg │ │ │ │ │ ├── logo-coinbase.svg │ │ │ │ │ ├── logo-facebook.svg │ │ │ │ │ ├── logo-google.svg │ │ │ │ │ ├── logo-mailchimp.svg │ │ │ │ │ ├── logo-nasa.svg │ │ │ │ │ ├── logo-netflix.svg │ │ │ │ │ ├── logo-pinterest.svg │ │ │ │ │ └── logo-spotify.svg │ │ │ │ ├── small-logos │ │ │ │ │ ├── logo-apple.svg │ │ │ │ │ ├── logo-asana.svg │ │ │ │ │ ├── logo-atlassian.svg │ │ │ │ │ ├── logo-google-drive.svg │ │ │ │ │ ├── logo-invision.svg │ │ │ │ │ ├── logo-jira.svg │ │ │ │ │ ├── logo-shopify.svg │ │ │ │ │ ├── logo-slack.svg │ │ │ │ │ ├── logo-spotify.svg │ │ │ │ │ ├── logo-weave.svg │ │ │ │ │ ├── logo-webdev.svg │ │ │ │ │ └── logo-xd.svg │ │ │ │ └── white-logos │ │ │ │ │ ├── logo-google-white.svg │ │ │ │ │ └── logo-spotify-white.svg │ │ │ ├── marie.jpg │ │ │ ├── meeting.jpg │ │ │ ├── nastuh.jpg │ │ │ ├── office-dark.jpg │ │ │ ├── shapes │ │ │ │ ├── pattern-lines.svg │ │ │ │ ├── waves-gray.svg │ │ │ │ └── waves-white.svg │ │ │ ├── team-1.jpg │ │ │ ├── team-2.jpg │ │ │ ├── team-3.jpg │ │ │ ├── team-4.jpg │ │ │ ├── toa-heftiba.jpg │ │ │ └── wave-1.svg │ │ ├── js │ │ │ ├── core │ │ │ │ ├── bootstrap.min.js │ │ │ │ └── popper.min.js │ │ │ ├── plugins │ │ │ │ ├── choices.min.js │ │ │ │ ├── countup.min.js │ │ │ │ ├── flatpickr.min.js │ │ │ │ ├── moment.min.js │ │ │ │ ├── parallax.min.js │ │ │ │ ├── perfect-scrollbar.min.js │ │ │ │ ├── prism.min.js │ │ │ │ ├── rellax.min.js │ │ │ │ ├── tilt.min.js │ │ │ │ └── typedjs.js │ │ │ ├── soft-design-system.js │ │ │ └── soft-design-system.min.js │ │ ├── package.json │ │ └── scss │ │ │ ├── soft-design-system.scss │ │ │ └── soft-design-system │ │ │ ├── _alert.scss │ │ │ ├── _buttons.scss │ │ │ ├── _cards.scss │ │ │ ├── _dropdown.scss │ │ │ ├── _footer.scss │ │ │ ├── _gradients.scss │ │ │ ├── _header.scss │ │ │ ├── _info-areas.scss │ │ │ ├── _misc.scss │ │ │ ├── _nav.scss │ │ │ ├── _navbar.scss │ │ │ ├── _pagination.scss │ │ │ ├── _popovers.scss │ │ │ ├── _progress.scss │ │ │ ├── _tooltips.scss │ │ │ ├── _typography.scss │ │ │ ├── _utilities.scss │ │ │ ├── _variables.scss │ │ │ ├── avatars │ │ │ ├── _avatar-group.scss │ │ │ └── _avatar.scss │ │ │ ├── bootstrap │ │ │ ├── _accordion.scss │ │ │ ├── _alert.scss │ │ │ ├── _badge.scss │ │ │ ├── _breadcrumb.scss │ │ │ ├── _button-group.scss │ │ │ ├── _buttons.scss │ │ │ ├── _card.scss │ │ │ ├── _carousel.scss │ │ │ ├── _close.scss │ │ │ ├── _containers.scss │ │ │ ├── _dropdown.scss │ │ │ ├── _forms.scss │ │ │ ├── _functions.scss │ │ │ ├── _grid.scss │ │ │ ├── _helpers.scss │ │ │ ├── _images.scss │ │ │ ├── _list-group.scss │ │ │ ├── _mixins.scss │ │ │ ├── _modal.scss │ │ │ ├── _nav.scss │ │ │ ├── _navbar.scss │ │ │ ├── _pagination.scss │ │ │ ├── _popover.scss │ │ │ ├── _progress.scss │ │ │ ├── _reboot.scss │ │ │ ├── _root.scss │ │ │ ├── _spinners.scss │ │ │ ├── _tables.scss │ │ │ ├── _toasts.scss │ │ │ ├── _tooltip.scss │ │ │ ├── _transitions.scss │ │ │ ├── _type.scss │ │ │ ├── _utilities.scss │ │ │ ├── _variables.scss │ │ │ ├── bootstrap-grid.scss │ │ │ ├── bootstrap-reboot.scss │ │ │ ├── bootstrap-utilities.scss │ │ │ ├── bootstrap.scss │ │ │ ├── forms │ │ │ │ ├── _floating-labels.scss │ │ │ │ ├── _form-check.scss │ │ │ │ ├── _form-control.scss │ │ │ │ ├── _form-range.scss │ │ │ │ ├── _form-select.scss │ │ │ │ ├── _form-text.scss │ │ │ │ ├── _input-group.scss │ │ │ │ ├── _labels.scss │ │ │ │ └── _validation.scss │ │ │ ├── helpers │ │ │ │ ├── _clearfix.scss │ │ │ │ ├── _colored-links.scss │ │ │ │ ├── _position.scss │ │ │ │ ├── _ratio.scss │ │ │ │ ├── _stretched-link.scss │ │ │ │ ├── _text-truncation.scss │ │ │ │ └── _visually-hidden.scss │ │ │ ├── mixins │ │ │ │ ├── _alert.scss │ │ │ │ ├── _border-radius.scss │ │ │ │ ├── _box-shadow.scss │ │ │ │ ├── _breakpoints.scss │ │ │ │ ├── _buttons.scss │ │ │ │ ├── _caret.scss │ │ │ │ ├── _clearfix.scss │ │ │ │ ├── _container.scss │ │ │ │ ├── _deprecate.scss │ │ │ │ ├── _forms.scss │ │ │ │ ├── _gradients.scss │ │ │ │ ├── _grid.scss │ │ │ │ ├── _image.scss │ │ │ │ ├── _list-group.scss │ │ │ │ ├── _lists.scss │ │ │ │ ├── _pagination.scss │ │ │ │ ├── _reset-text.scss │ │ │ │ ├── _resize.scss │ │ │ │ ├── _table-variants.scss │ │ │ │ ├── _text-truncate.scss │ │ │ │ ├── _transition.scss │ │ │ │ ├── _utilities.scss │ │ │ │ └── _visually-hidden.scss │ │ │ ├── utilities │ │ │ │ └── _api.scss │ │ │ └── vendor │ │ │ │ └── _rfs.scss │ │ │ ├── cards │ │ │ └── card-background.scss │ │ │ ├── custom │ │ │ ├── _styles.scss │ │ │ └── _variables.scss │ │ │ ├── forms │ │ │ ├── _form-check.scss │ │ │ ├── _form-select.scss │ │ │ ├── _form-switch.scss │ │ │ ├── _forms.scss │ │ │ ├── _input-group.scss │ │ │ ├── _inputs.scss │ │ │ └── _labels.scss │ │ │ ├── mixins │ │ │ ├── _hover.scss │ │ │ └── mixins.scss │ │ │ ├── theme.scss │ │ │ ├── variables │ │ │ ├── _animations.scss │ │ │ ├── _avatars.scss │ │ │ ├── _cards.scss │ │ │ ├── _choices.scss │ │ │ ├── _dropdowns.scss │ │ │ ├── _form-switch.scss │ │ │ ├── _header.scss │ │ │ ├── _info-areas.scss │ │ │ ├── _misc.scss │ │ │ ├── _navbar.scss │ │ │ ├── _pagination.scss │ │ │ └── _utilities.scss │ │ │ └── vendor │ │ │ ├── _choices.scss │ │ │ ├── _flatpickr.scss │ │ │ ├── _nouislider.scss │ │ │ ├── _prism.scss │ │ │ └── plugins.scss │ ├── favicon.ico │ └── sitemap.xml └── templates │ ├── .gitkeep │ ├── accounts │ ├── login.html │ └── register.html │ ├── home │ ├── attention-catchers-alerts.html │ ├── attention-catchers-modals.html │ ├── attention-catchers-tooltips-popovers.html │ ├── elements-avatars.html │ ├── elements-badges.html │ ├── elements-breadcrumbs.html │ ├── elements-buttons.html │ ├── elements-dropdowns.html │ ├── elements-progress-bars.html │ ├── elements-toggles.html │ ├── elements-typography.html │ ├── index.html │ ├── input-areas-forms.html │ ├── input-areas-inputs.html │ ├── navigation-nav-tabs.html │ ├── navigation-navbars.html │ ├── navigation-pagination.html │ ├── page-403.html │ ├── page-404.html │ ├── page-500.html │ ├── page-about-us.html │ ├── page-author.html │ ├── page-contact-us.html │ ├── page-sections-features.html │ ├── page-sections-hero-sections.html │ ├── page-sign-in.html │ └── page-sign-up.html │ ├── includes │ ├── footer.html │ ├── navigation-auth.html │ ├── navigation-light.html │ ├── navigation-transparent.html │ ├── navigation.html │ └── scripts.html │ └── layouts │ ├── base-fullscreen.html │ ├── base-presentation.html │ └── base.html ├── build.sh ├── docker-compose.yml ├── env.sample ├── gunicorn-cfg.py ├── media ├── flask-soft-ui-free-screen-blocks.png ├── flask-soft-ui-free-screen-buttons.png ├── flask-soft-ui-free-screen-cards.png ├── flask-soft-ui-free-screen-contact.png ├── flask-soft-ui-free-screen-login.png ├── flask-soft-ui-free-screen-sample-pages.png ├── flask-soft-ui-free-screen-subscribe.png ├── flask-soft-ui-free-screen-team.png └── flask-soft-ui-free-screen.png ├── nginx └── appseed-app.conf ├── render.yaml ├── requirements.txt └── run.py /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | __pycache__ 3 | *.pyc 4 | *.pyo 5 | *.pyd -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # True for development, False for production 2 | DEBUG=False 3 | 4 | # Flask ENV 5 | FLASK_APP=run.py 6 | FLASK_DEBUG=True 7 | 8 | # If not provided, a random one is generated 9 | # SECRET_KEY= 10 | 11 | # Used for CDN (in production) 12 | # No Slash at the end 13 | ASSETS_ROOT=/static/assets 14 | 15 | # If DB credentials (if NOT provided, or wrong values SQLite is used) 16 | # DB_ENGINE=mysql 17 | # DB_HOST=localhost 18 | # DB_NAME=appseed_db 19 | # DB_USERNAME=appseed_db_usr 20 | # DB_PASS=pass 21 | # DB_PORT=3306 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # tests and coverage 6 | *.pytest_cache 7 | .coverage 8 | 9 | # database & logs 10 | *.db 11 | *.sqlite3 12 | *.log 13 | 14 | # venv 15 | env 16 | venv 17 | 18 | # other 19 | .DS_Store 20 | 21 | # sphinx docs 22 | _build 23 | _static 24 | _templates 25 | 26 | # javascript 27 | package-lock.json 28 | .vscode/symbols.json 29 | 30 | apps/static/assets/node_modules 31 | apps/static/assets/yarn.lock 32 | apps/static/assets/.temp 33 | 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.0.13] 2024-10-28 4 | ### Changes 5 | 6 | - Added `Deploy on Render` Button 7 | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy) 8 | 9 | ## [1.0.12] 2024-10-28 10 | ### Changes 11 | 12 | - `SECRET_KEY`: remove the random string if not found 13 | - Update Versions 14 | - Fix for Python 13 15 | 16 | ## [1.0.11] 2024-05-18 17 | ### Changes 18 | 19 | - Updated DOCS (readme) 20 | - [Custom Development](https://appseed.us/custom-development/) Section 21 | - [CI/CD Assistance for AWS, DO](https://appseed.us/terms/#section-ci-cd) 22 | 23 | ## [1.0.10] 2024-03-09 24 | ### Changes 25 | 26 | - Update [Custom Development](https://appseed.us/custom-development/) Section 27 | - New Pricing: `$3,999` 28 | 29 | ## [1.0.9] 2023-01-03 30 | ### Changes 31 | 32 | - `DOCS Update` (readme) 33 | - [Flask Soft Design - Go LIVE](https://www.youtube.com/watch?v=XtRlKDNW5R0) (`video presentation`) 34 | 35 | ## [1.0.8] 2023-01-03 36 | ### Changes 37 | 38 | - Deployment-ready for Render (CI/CD) 39 | - `render.yaml` 40 | - `build.sh` 41 | - `DB Management` Improvement 42 | - `Silent fallback` to **SQLite** 43 | 44 | ## [1.0.7] 2022-11-13 45 | ### Improvements 46 | 47 | - Compatible with [LIVE Deployer](https://appseed.us/go-live/) 48 | - `Drag & Drop` deployment service 49 | 50 | ## [1.0.6] 2022-05-31 51 | ### Improvements 52 | 53 | - Built with [Soft UI Design Generator](https://appseed.us/generator/soft-ui-design/) 54 | - Timestamp: `2022-05-31 08:48` 55 | 56 | ## [1.0.5] 2022-01-17 57 | ### Improvements 58 | 59 | - Bump Flask Codebase to [v2.0.0](https://github.com/app-generator/boilerplate-code-flask/releases) 60 | - Dependencies update (all packages) 61 | - Flask==2.0.2 (latest stable version) 62 | - flask_wtf==1.0.0 63 | - jinja2==3.0.3 64 | - flask-restx==0.5.1 65 | 66 | ## [1.0.4] 2021-01-06 67 | ### Improvements (minor) 68 | 69 | - Update Product Links 70 | - Fix Broken Links 71 | 72 | ## [1.0.3] 2021-12-12 73 | ### Dependencies Update 74 | 75 | - Bump [Flask Codebase](https://github.com/app-generator/boilerplate-code-flask) v1.0.7 76 | - Dependencies update (all packages) 77 | - Flask==2.0.1 (latest stable version) 78 | - Better Code formatting 79 | - Improved Files organization 80 | - Optimize imports 81 | - Docker Scripts Update 82 | - Gulp Tooling (SASS Compilation) 83 | 84 | ## [1.0.2] 2021-05-16 85 | ### Dependencies Update 86 | 87 | - Bump [Flask Codebase](https://github.com/app-generator/boilerplate-code-flask) v1.0.5 88 | - Freeze used versions in `requirements.txt` 89 | - jinja2 = 2.11.3 90 | 91 | ## [1.0.1] 2020-03-25 92 | ### Improvements 93 | 94 | - Bump [Flask Codebase](https://github.com/app-generator/boilerplate-code-flask) v1.0.4 95 | - Freeze used versions in `requirements.txt` 96 | - flask_sqlalchemy = 2.4.4 97 | - sqlalchemy = 1.3.23 98 | 99 | ## [1.0.0] 2020-03-09 100 | ### Initial Release 101 | 102 | - Soft UI Design System [v1.0.1](https://github.com/creativetimofficial/soft-ui-design-system/releases) 103 | - Flask Codebase [v1.0.3](https://github.com/app-generator/boilerplate-code-flask) 104 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9 2 | 3 | # set environment variables 4 | ENV PYTHONDONTWRITEBYTECODE 1 5 | ENV PYTHONUNBUFFERED 1 6 | 7 | COPY requirements.txt . 8 | 9 | # install python dependencies 10 | RUN pip install --upgrade pip 11 | RUN pip install --no-cache-dir -r requirements.txt 12 | 13 | COPY . . 14 | 15 | # gunicorn 16 | CMD ["gunicorn", "--config", "gunicorn-cfg.py", "run:app"] 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 - present [AppSeed](http://appseed.us/) 4 | 5 |
6 | 7 | ## Licensing Information 8 | 9 |
10 | 11 | | Item | - | 12 | | ---------------------------------- | --- | 13 | | License Type | MIT | 14 | | Use for print | **YES** | 15 | | Create single personal website/app | **YES** | 16 | | Create single website/app for client | **YES** | 17 | | Create multiple website/apps for clients | **YES** | 18 | | Create multiple SaaS applications | **YES** | 19 | | End-product paying users | **YES** | 20 | | Product sale | **YES** | 21 | | Remove footer credits | **YES** | 22 | | --- | --- | 23 | | Remove copyright mentions from source code | NO | 24 | | Production deployment assistance | NO | 25 | | Create HTML/CSS template for sale | NO | 26 | | Create Theme/Template for CMS for sale | NO | 27 | | Separate sale of our UI Elements | NO | 28 | 29 |
30 | 31 | --- 32 | For more information regarding licensing, please contact the AppSeed Service < *support@appseed.us* > 33 | -------------------------------------------------------------------------------- /apps/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | import os 7 | 8 | from flask import Flask 9 | from flask_login import LoginManager 10 | from flask_sqlalchemy import SQLAlchemy 11 | from importlib import import_module 12 | 13 | 14 | db = SQLAlchemy() 15 | login_manager = LoginManager() 16 | 17 | 18 | def register_extensions(app): 19 | db.init_app(app) 20 | login_manager.init_app(app) 21 | 22 | 23 | def register_blueprints(app): 24 | for module_name in ('authentication', 'home'): 25 | module = import_module('apps.{}.routes'.format(module_name)) 26 | app.register_blueprint(module.blueprint) 27 | 28 | 29 | def configure_database(app): 30 | 31 | @app.before_first_request 32 | def initialize_database(): 33 | try: 34 | db.create_all() 35 | except Exception as e: 36 | 37 | print('> Error: DBMS Exception: ' + str(e) ) 38 | 39 | # fallback to SQLite 40 | basedir = os.path.abspath(os.path.dirname(__file__)) 41 | app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'db.sqlite3') 42 | 43 | print('> Fallback to SQLite ') 44 | db.create_all() 45 | 46 | @app.teardown_request 47 | def shutdown_session(exception=None): 48 | db.session.remove() 49 | 50 | 51 | def create_app(config): 52 | app = Flask(__name__) 53 | app.config.from_object(config) 54 | register_extensions(app) 55 | register_blueprints(app) 56 | configure_database(app) 57 | return app 58 | -------------------------------------------------------------------------------- /apps/authentication/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from flask import Blueprint 7 | 8 | blueprint = Blueprint( 9 | 'authentication_blueprint', 10 | __name__, 11 | url_prefix='' 12 | ) 13 | -------------------------------------------------------------------------------- /apps/authentication/forms.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from flask_wtf import FlaskForm 7 | from wtforms import StringField, PasswordField 8 | from wtforms.validators import Email, DataRequired 9 | 10 | # login and registration 11 | 12 | 13 | class LoginForm(FlaskForm): 14 | username = StringField('Username', 15 | id='username_login', 16 | validators=[DataRequired()]) 17 | password = PasswordField('Password', 18 | id='pwd_login', 19 | validators=[DataRequired()]) 20 | 21 | 22 | class CreateAccountForm(FlaskForm): 23 | username = StringField('Username', 24 | id='username_create', 25 | validators=[DataRequired()]) 26 | email = StringField('Email', 27 | id='email_create', 28 | validators=[DataRequired(), Email()]) 29 | password = PasswordField('Password', 30 | id='pwd_create', 31 | validators=[DataRequired()]) 32 | -------------------------------------------------------------------------------- /apps/authentication/models.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from flask_login import UserMixin 7 | 8 | from apps import db, login_manager 9 | 10 | from apps.authentication.util import hash_pass 11 | 12 | class Users(db.Model, UserMixin): 13 | 14 | __tablename__ = 'Users' 15 | 16 | id = db.Column(db.Integer, primary_key=True) 17 | username = db.Column(db.String(64), unique=True) 18 | email = db.Column(db.String(64), unique=True) 19 | password = db.Column(db.LargeBinary) 20 | 21 | def __init__(self, **kwargs): 22 | for property, value in kwargs.items(): 23 | # depending on whether value is an iterable or not, we must 24 | # unpack it's value (when **kwargs is request.form, some values 25 | # will be a 1-element list) 26 | if hasattr(value, '__iter__') and not isinstance(value, str): 27 | # the ,= unpack of a singleton fails PEP8 (travis flake8 test) 28 | value = value[0] 29 | 30 | if property == 'password': 31 | value = hash_pass(value) # we need bytes here (not plain str) 32 | 33 | setattr(self, property, value) 34 | 35 | def __repr__(self): 36 | return str(self.username) 37 | 38 | 39 | @login_manager.user_loader 40 | def user_loader(id): 41 | return Users.query.filter_by(id=id).first() 42 | 43 | 44 | @login_manager.request_loader 45 | def request_loader(request): 46 | username = request.form.get('username') 47 | user = Users.query.filter_by(username=username).first() 48 | return user if user else None 49 | -------------------------------------------------------------------------------- /apps/authentication/util.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | import os 7 | import hashlib 8 | import binascii 9 | 10 | # Inspiration -> https://www.vitoshacademy.com/hashing-passwords-in-python/ 11 | 12 | 13 | def hash_pass(password): 14 | """Hash a password for storing.""" 15 | 16 | salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii') 17 | pwdhash = hashlib.pbkdf2_hmac('sha512', password.encode('utf-8'), 18 | salt, 100000) 19 | pwdhash = binascii.hexlify(pwdhash) 20 | return (salt + pwdhash) # return bytes 21 | 22 | 23 | def verify_pass(provided_password, stored_password): 24 | """Verify a stored password against one provided by user""" 25 | 26 | stored_password = stored_password.decode('ascii') 27 | salt = stored_password[:64] 28 | stored_password = stored_password[64:] 29 | pwdhash = hashlib.pbkdf2_hmac('sha512', 30 | provided_password.encode('utf-8'), 31 | salt.encode('ascii'), 32 | 100000) 33 | pwdhash = binascii.hexlify(pwdhash).decode('ascii') 34 | return pwdhash == stored_password 35 | -------------------------------------------------------------------------------- /apps/config.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | import os, random, string 7 | 8 | class Config(object): 9 | 10 | basedir = os.path.abspath(os.path.dirname(__file__)) 11 | 12 | # Assets Management 13 | ASSETS_ROOT = os.getenv('ASSETS_ROOT', '/static/assets') 14 | 15 | # Set up the App SECRET_KEY 16 | SECRET_KEY = os.getenv('SECRET_KEY', 'Secr3t_55xA') 17 | 18 | SQLALCHEMY_TRACK_MODIFICATIONS = False 19 | 20 | DB_ENGINE = os.getenv('DB_ENGINE' , None) 21 | DB_USERNAME = os.getenv('DB_USERNAME' , None) 22 | DB_PASS = os.getenv('DB_PASS' , None) 23 | DB_HOST = os.getenv('DB_HOST' , None) 24 | DB_PORT = os.getenv('DB_PORT' , None) 25 | DB_NAME = os.getenv('DB_NAME' , None) 26 | 27 | USE_SQLITE = True 28 | 29 | # try to set up a Relational DBMS 30 | if DB_ENGINE and DB_NAME and DB_USERNAME: 31 | 32 | try: 33 | 34 | # Relational DBMS: PSQL, MySql 35 | SQLALCHEMY_DATABASE_URI = '{}://{}:{}@{}:{}/{}'.format( 36 | DB_ENGINE, 37 | DB_USERNAME, 38 | DB_PASS, 39 | DB_HOST, 40 | DB_PORT, 41 | DB_NAME 42 | ) 43 | 44 | USE_SQLITE = False 45 | 46 | except Exception as e: 47 | 48 | print('> Error: DBMS Exception: ' + str(e) ) 49 | print('> Fallback to SQLite ') 50 | 51 | if USE_SQLITE: 52 | 53 | # This will create a file in FOLDER 54 | SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'db.sqlite3') 55 | 56 | class ProductionConfig(Config): 57 | DEBUG = False 58 | 59 | # Security 60 | SESSION_COOKIE_HTTPONLY = True 61 | REMEMBER_COOKIE_HTTPONLY = True 62 | REMEMBER_COOKIE_DURATION = 3600 63 | 64 | class DebugConfig(Config): 65 | DEBUG = True 66 | 67 | 68 | # Load all possible configurations 69 | config_dict = { 70 | 'Production': ProductionConfig, 71 | 'Debug' : DebugConfig 72 | } 73 | -------------------------------------------------------------------------------- /apps/home/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from flask import Blueprint 7 | 8 | blueprint = Blueprint( 9 | 'home_blueprint', 10 | __name__, 11 | url_prefix='' 12 | ) 13 | -------------------------------------------------------------------------------- /apps/home/routes.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from apps.home import blueprint 7 | from flask import render_template, request 8 | from flask_login import login_required 9 | from jinja2 import TemplateNotFound 10 | 11 | 12 | @blueprint.route('/index') 13 | @login_required 14 | def index(): 15 | 16 | return render_template('home/index.html', segment='index') 17 | 18 | 19 | @blueprint.route('/