')
76 | def react_root(path):
77 | if path == 'favicon.ico':
78 | return app.send_static_file('favicon.ico')
79 | return app.send_static_file('index.html')
80 |
--------------------------------------------------------------------------------
/app/api/auth_routes.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint, jsonify, session, request
2 | from app.models import User, db
3 | from app.forms import LoginForm
4 | from app.forms import SignUpForm
5 | from flask_login import current_user, login_user, logout_user, login_required
6 |
7 | auth_routes = Blueprint('auth', __name__)
8 |
9 |
10 | def validation_errors_to_error_messages(validation_errors):
11 | """
12 | Simple function that turns the WTForms validation errors into a simple list
13 | """
14 | errorMessages = []
15 | for field in validation_errors:
16 | for error in validation_errors[field]:
17 | errorMessages.append(f"{field} : {error}")
18 | return errorMessages
19 |
20 |
21 | @auth_routes.route('/')
22 | def authenticate():
23 | """
24 | Authenticates a user.
25 | """
26 | if current_user.is_authenticated:
27 | return current_user.to_dict()
28 | return {'errors': ['Unauthorized']}, 401
29 |
30 |
31 | @auth_routes.route('/login', methods=['POST'])
32 | def login():
33 | """
34 | Logs a user in
35 | """
36 | form = LoginForm()
37 | # Get the csrf_token from the request cookie and put it into the
38 | # form manually to validate_on_submit can be used
39 | form['csrf_token'].data = request.cookies['csrf_token']
40 | if form.validate_on_submit():
41 | # Add the user to the session, we are logged in!
42 | user = User.query.filter(User.email == form.data['email']).first()
43 | login_user(user)
44 | return user.to_dict()
45 | return {'errors': validation_errors_to_error_messages(form.errors)}, 401
46 |
47 |
48 | @auth_routes.route('/logout')
49 | def logout():
50 | """
51 | Logs a user out
52 | """
53 | logout_user()
54 | return {'message': 'User logged out'}
55 |
56 |
57 | @auth_routes.route('/signup', methods=['POST'])
58 | def sign_up():
59 | """
60 | Creates a new user and logs them in
61 | """
62 | form = SignUpForm()
63 | form['csrf_token'].data = request.cookies['csrf_token']
64 | if form.validate_on_submit():
65 | user = User(
66 | first_name=form.data['first_name'],
67 | last_name=form.data['last_name'],
68 | email=form.data['email'],
69 | password=form.data['password']
70 | )
71 | db.session.add(user)
72 | db.session.commit()
73 | login_user(user)
74 |
75 | return user.to_dict()
76 | return {'errors': validation_errors_to_error_messages(form.errors)}
77 |
78 |
79 | @auth_routes.route('/unauthorized')
80 | def unauthorized():
81 | """
82 | Returns unauthorized JSON when flask-login authentication fails
83 | """
84 | return {'errors': ['Unauthorized']}, 401
85 |
--------------------------------------------------------------------------------
/.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 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
134 | # pytype static type analyzer
135 | .pytype/
136 |
137 | # Cython debug symbols
138 | cython_debug/
139 | keys.js
140 | apikey.js
--------------------------------------------------------------------------------
/react-app/src/components/auth/LoginForm.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Redirect } from "react-router-dom";
3 | import { login } from "../../services/auth";
4 | import { Modal, Button } from "react-bootstrap"
5 |
6 |
7 | const LoginModal = (props) => {
8 | const [errors, setErrors] = useState([]);
9 | const [email, setEmail] = useState("");
10 | const [password, setPassword] = useState("");
11 | const { authenticated, setAuthenticated } = props
12 |
13 | const switchToSignup = () => {
14 | props.setShowLogin(false);
15 | props.setShowSignUp(true);
16 | };
17 |
18 | const onLogin = async (e) => {
19 | e.preventDefault();
20 | const user = await login(email, password);
21 | if (!user.errors) {
22 | setAuthenticated(true);
23 | } else {
24 | setErrors(user.errors);
25 | props.setShowLogin(true)
26 | }
27 | };
28 |
29 | const onDemoLogin = async (e) => {
30 | e.preventDefault();
31 | const user = await login('demo@aa.io', 'password');
32 | if (!user.errors) {
33 | setAuthenticated(true);
34 | } else {
35 | setErrors(user.errors);
36 | }
37 | };
38 |
39 | const updateEmail = (e) => {
40 | setEmail(e.target.value);
41 | };
42 |
43 | const updatePassword = (e) => {
44 | setPassword(e.target.value);
45 | };
46 |
47 | if (authenticated) {
48 | return ;
49 | }
50 |
51 | return (
52 |
58 |
59 |
60 | Login
61 |
62 |
63 |
64 |
96 |
97 |
98 | Close
99 |
100 |
101 | );
102 | }
103 |
104 | export default LoginModal
--------------------------------------------------------------------------------
/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',
27 | str(current_app.extensions['migrate'].db.engine.url).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 |
--------------------------------------------------------------------------------
/react-app/src/components/SearchComponent/SearchPage/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import queryString from 'query-string';
4 | import Button from 'react-bootstrap/Button'
5 | import { ArrowDownCircle } from 'react-bootstrap-icons'
6 | import { useLocation } from 'react-router-dom';
7 |
8 | import { googleApiKey } from '../../GoogleMapsComponent/apikey'
9 | import SpotViewMini from '../../SpotComponent/SpotViewMini'
10 | import SearchBar from '../SearchBar'
11 | import { searchSpots } from '../../../store/spot';
12 | import MySearchMap from './searchmap';
13 | import './search-page.css';
14 |
15 | const SearchPage = () => {
16 | const dispatch = useDispatch()
17 | const { search } = useLocation()
18 | const { city, guest } = queryString.parse(search)
19 |
20 | const [selectedSpot, setSelectedSpot] = useState(0)
21 | const [selectedPark, setSelectedPark] = useState(null)
22 |
23 | const spots_state = useSelector(state => state.spots.searched_spots)
24 |
25 |
26 | useEffect(() => {
27 | dispatch(searchSpots(guest, city))
28 | }, [dispatch])
29 |
30 | const handleSearch = (spots_state) => {
31 | let spot_obj = []
32 | for (let spot in spots_state) {
33 | spot_obj.push(spots_state[spot])
34 | }
35 | return spot_obj.map((spot, idx) => (
36 |
37 |
38 | setSelectedPark(spot)}>View map
39 |
40 | ))
41 | }
42 |
43 | const grammar = (amount) => {
44 | if (amount == 1) return 'person'
45 | return 'people'
46 | }
47 |
48 | const downCirc = () => {
49 | return ( )
50 | }
51 |
52 | return (
53 |
54 |
55 | {
Spots available to book in '{city}' for {guest} {grammar(guest)} }
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | {spots_state && spots_state.length === 0 &&
Please alter your search }
64 | {handleSearch(spots_state)}
65 |
66 |
67 | {spots_state &&
}
70 | containerElement={
}
71 | mapElement={
}
72 | position={spots_state[selectedSpot] ? { lat: spots_state[selectedSpot].latitude, lng: spots_state[selectedSpot].longitude } : { lat: 20, lng: 20 }}
73 | allSpots={spots_state}
74 | selectedPark={selectedPark}
75 | setSelectedPark={setSelectedPark}
76 | />}
77 |
78 |
79 |
80 |
81 | )
82 | };
83 |
84 | export default SearchPage
--------------------------------------------------------------------------------
/react-app/src/components/HomePageComponent/home-page.css:
--------------------------------------------------------------------------------
1 | .welcome-image {
2 | object-fit: cover;
3 | height: 500px;
4 | width: 100%;
5 | /* filter: blur(1px); */
6 | }
7 |
8 | .home-search {
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | /* position: sticky; */
13 | /* top: 15px; */
14 | /* z-index: 1030; */
15 | background: 0;
16 | /* width: 80%; */
17 | }
18 |
19 | /* @media screen and (max-width: 1360px) {
20 | .home-search {
21 | position: fixed;
22 | z-index: 2;
23 | top: 90px;
24 | width: 100%;
25 | }
26 |
27 | .search-bar-container {
28 | width: 100%;
29 | border-radius: 0;
30 | background-color: #E2DADB;
31 | }
32 | } */
33 |
34 | .search-container {
35 | /* display: relative; */
36 | top: -500px;
37 | }
38 |
39 | #namaste {
40 | height: 100px;
41 | }
42 |
43 | #carouselitem {
44 | height: 300px;
45 | text-align: center;
46 | padding-top: 40px;
47 | }
48 |
49 |
50 | .carousel-caption p, .carousel-control-prev, .carousel-control-next {
51 | color: #A2A7A5
52 | }
53 |
54 | .picture-color {
55 | position: relative;
56 | top: -80px;
57 | width: 100%;
58 | height: 80px;
59 | background-color: #E2DADB;
60 | }
61 |
62 | #mission-statement {
63 | font-size: 35px;
64 | font-family: 'Josefin Sans';
65 | }
66 |
67 | .spot-view-large-body {
68 | background-color: lightgray;
69 | transition: box-shadow .3s;
70 | height: 550px;
71 | min-width: 400px;
72 | width: 400px;
73 | margin: 20px 20px;
74 | padding: 20px;
75 | border-radius: 0.5em;
76 | }
77 |
78 | .spot-view-large-body:hover {
79 | box-shadow: 0 0 11px rgba(0, 0, 0, 0.26);
80 | }
81 |
82 | .home-body {
83 | width: 100%;
84 | height: 100%;
85 | display: flex;
86 | flex-direction: column;
87 | align-items: center;
88 | position: relative;
89 | top: -33px;
90 | }
91 |
92 | .home-search-bar-container {
93 | display: flex;
94 | flex-direction: column;
95 | justify-content: center;
96 | align-items: center;
97 | height: 600px;
98 | width: 100%;
99 | }
100 |
101 | .welcome-search-container {
102 | height: 500px;
103 | width: 100%;
104 | position: relative;
105 | top: -30px;
106 | }
107 |
108 |
109 | .newest-spots-container {
110 | width: 100%;
111 | display: flex;
112 | flex-wrap: nowrap;
113 | overflow-x: scroll;
114 | margin: 50px 20px;
115 | }
116 | .newest-spots-container .spotcard {
117 | background-color: #DAE2DF;
118 | padding: 20px;
119 | margin: 10px;
120 | min-width: 300px;
121 | max-width: 300px;
122 | box-shadow: 0 1px 5px #A2A7A5;
123 | transition: all .2s ease-in-out;
124 | }
125 |
126 |
127 | .mission-container {
128 | display: grid;
129 | grid-template-columns: 70% 30%;
130 | height: 300px;
131 | width: 100%;
132 | }
133 |
134 | .mission-statement-container {
135 | display: flex;
136 | flex-direction: column;
137 | justify-content: center;
138 | align-items: center;
139 | padding: 0px 50px;
140 | }
141 |
142 | .amount-sheltered-container {
143 | /* background-color: #DAE2DF; */
144 | /* display: flex; */
145 | /* flex-direction: column;
146 | justify-content: center;
147 | align-items: center; */
148 | }
149 |
150 | .google-maps-container {
151 | height: 500px;
152 | width: 100%;
153 | background-color: lightslategrey;
154 | }
--------------------------------------------------------------------------------
/react-app/src/components/NavBar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useHistory } from "react-router-dom";
3 | import { Navbar, Nav, Button } from "react-bootstrap";
4 |
5 | import LogoutButton from "./auth/LogoutButton";
6 |
7 | const NavBar = ({ authenticated, setAuthenticated, setShowLogin, setShowSignUp }) => {
8 |
9 | const history = useHistory();
10 |
11 | const hostASpot = (e) => {
12 | e.preventDefault();
13 |
14 | if (authenticated) {
15 | history.push('/spot/create')
16 | } else {
17 | setShowSignUp(true)
18 | }
19 | };
20 |
21 | return (
22 |
29 |
30 |
31 |
37 | CareBnb
38 |
39 |
40 |
41 |
42 | Host a Spot
43 |
44 | {!authenticated && (
45 |
46 | setShowLogin(true)}>Log in
47 |
48 | )}
49 |
50 | {authenticated && }
51 |
52 | {/* }
54 | id="basic-nav-dropdown"
55 | className="dropbottom"
56 | style={{ zIndex: 1400 }}
57 | > */}
58 | {/*
59 |
60 | Home
61 |
62 | {authenticated == true && (
63 | <>
64 |
65 | Manage Spots
66 |
67 |
68 | Account
69 |
70 | >)}
71 |
72 | FAQ
73 |
74 |
75 |
76 | {authenticated === false && (
77 | <>
78 |
79 | setShowLogin(true)}>Login
80 |
81 |
82 | setShowSignUp(true)}>Sign Up
83 |
84 | {/* */}
85 | {/* >
86 | )} */}
87 | {/* {authenticated === true && (
88 | <>
89 |
90 | Welcome user
91 |
92 |
93 |
94 |
95 | >
96 | )}
97 | */}
98 |
99 |
100 | );
101 | };
102 |
103 | export default NavBar;
104 |
--------------------------------------------------------------------------------
/react-app/src/components/SpotComponent/SpotPage/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useParams } from "react-router-dom";
3 | import { useSelector, useDispatch } from "react-redux";
4 |
5 | import BookingPageComponent from "../../BookingPageComponent/index";
6 | import FundingComponent from "../../FundingComponent/index"
7 | import "./SpotPage.css"
8 | import { getSpot } from "../../../store/spot";
9 |
10 |
11 | const SpotPage = () => {
12 | const dispatch = useDispatch()
13 | const spotId = Number.parseInt(useParams().spotId)
14 | const spotSelector = useSelector(state => state.spots)
15 | const spotAvailability = useSelector(state => state.spots.availability)
16 | // const spotTitle = spotSelector.spot.title
17 |
18 | const [spotState, setSpotState] = useState("")
19 |
20 | useEffect(() => {
21 | setSpotState(dispatch(getSpot(spotId)))
22 | }, [dispatch])
23 |
24 |
25 | return (
26 | <>
27 |
28 | {spotSelector.spot && (
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
{spotSelector.spot.title}
37 |
38 | {spotSelector.spot.city}, {spotSelector.spot.state}
39 |
40 | {spotSelector.spot.description}
41 |
42 | Max Capacity: {spotSelector.spot.capacity}
43 | Current Availability: {spotSelector.spot.availability}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | //
58 | //
{spotSelector.spot.title}
59 | //
{spotSelector.spot.city}, {spotSelector.spot.state}
60 | //
{spotSelector.spot.description}
61 | //
Max Capacity: {spotSelector.spot.capacity}
62 | //
Current Availability: {spotSelector.spot.availability}
63 | //
64 | //
65 | //
66 | //
67 |
68 | )}
69 |
70 | >
71 | )
72 |
73 | }
74 |
75 | export default SpotPage
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CareBnB
2 |
3 | ---
4 |
5 | **CareBnB** is a clone of **[AirBnB](https://www.airbnb.com/)** but instead of serving guests who are traveling, CareBnB serves those in need of shelter. The project was inspired by the 2021 events in Northern Texas, where many Texans were left without power and water.
6 |
7 | Try the site live: Here | Check out our documentation
8 |
9 | ## How to run the site locally
10 |
11 | - Clone the repo
12 | - In the route directory, use the command ```pipenv install``` all dependencies
13 | - In the react-app directory, use the command ```npm install``` to install all dependencies
14 | - Make a copy of the .env.example file and edit to match local db configuration
15 | - Create a file in /CareBnB/react-app/src/components/GoogleMapsComponent called apikey.js with one export const named "googleApiKey"
16 | - Create a file in /CareBnB/react-app/src/aws called keys.js with one export const named "SECRET_ACCESS_KEY"
17 | - Create the database and user in psql
18 | * In a pipenv shell, run all migrations with ```flask db upgrade```
19 | * Seed all data with ```flask db seed all```
20 | - In the route directory, use the command ```flask run``` to run the backend server
21 | - In the react-app directory, use the comman ```npm start``` to run the front end
22 |
23 | ## Technologies used in CareBnB
24 |
25 | **JavaScript**
26 |
27 | **Python**
28 |
29 | **SQLAlchemy**
30 |
31 | **Flask**
32 |
33 | **React**
34 |
35 | **Redux**
36 |
37 | **React Google Maps**
38 |
39 | **Amazon Web Services**
40 |
41 | **React Bootstrap**
42 |
43 | **Heroku**
44 |
45 | ## Features that we implemented
46 |
47 |
48 | * Users can **log in** or **sign up** to access some functionality of the site.
49 | * A logged in user has the ability to **host a spot** that displays how many guests they can accept.
50 | * A logged in user can **book a spot**, and selecte how many guests they are booking for.
51 | * The **search** bar can locate using a search term and specify how many available spots.
52 | * A logged in user can utilize **google maps** to identify a spot close to them.
53 |
54 | ## Challenges throughout the development process
55 | We faced a few challenges while we were building CareBnB:
56 |
57 | 1. Implementing google maps to allow users to search spots by their coordinates
58 |
59 | 2. Implementing AWS to allow users to upload an image as a file instead of a image url
60 |
61 | ## Developers
62 |
63 |
64 |
84 |
85 |
--------------------------------------------------------------------------------
/react-app/src/components/HomePageComponent/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { Carousel } from "react-bootstrap";
4 | import { getTopAvailableSpots } from "../../store/spot";
5 | import { googleApiKey } from "../GoogleMapsComponent/apikey";
6 | import SearchBar from "../SearchComponent/SearchBar";
7 | import SpotViewMini from "../SpotComponent/SpotViewMini";
8 | import { WrappedGoogleMap } from "../GoogleMapsComponent/index";
9 | import LoginModal from "../auth/LoginForm";
10 | import SignUpModal from "../auth/SignUpForm";
11 | import FooterComponent from "../FooterComponent"
12 |
13 | import "./home-page.css";
14 | import NewUser from "./newUser";
15 |
16 | const HomePageComponent = (props) => {
17 | const dispatch = useDispatch();
18 | const { authenticated, setShowLogin, setShowSignUp } = props
19 | const [show, setShow] = useState(true);
20 | const available_spots = useSelector((state) => state.spots.available_spots);
21 |
22 | useEffect(() => {
23 | dispatch(getTopAvailableSpots());
24 | }, [dispatch]);
25 |
26 | return (
27 |
28 |
29 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | {authenticated === false &&
}
41 |
42 |
Experience a spot
43 |
44 | {available_spots &&
45 | available_spots.map((spot, idx) => (
46 |
47 | ))}
48 |
49 |
50 |
51 |
52 |
53 | "We aim to ensure no human being has to go to sleep without
54 | shelter...."
55 |
56 |
57 |
58 |
59 |
60 | 230
61 |
62 | People currently sheltered
63 |
64 |
65 |
66 | Thank you username for your donation
67 |
72 |
73 | Donated 2 minutes ago
74 |
75 |
76 |
77 |
78 |
79 |
82 |
83 |
}
86 | containerElement={
}
87 | mapElement={
}
88 | />
89 |
90 |
91 |
92 | );
93 | };
94 |
95 | export default HomePageComponent;
96 |
--------------------------------------------------------------------------------
/react-app/src/store/spot.js:
--------------------------------------------------------------------------------
1 | const LOAD = "spot/getAllSpots";
2 | const LOAD_ONE = "spot/getOneSpot";
3 | const LOAD_AVAILABLE = "spot/getAvailableSpots";
4 | const LOAD_SEARCH = "spot/getAllSearchedSpots";
5 | const CREATE_SPOT = "spot/createNewSpot";
6 | const DELETE_SPOT = "spot/deleteASpot";
7 |
8 | const getAllSearchedSpots = (spots) => {
9 | return {
10 | type: LOAD_SEARCH,
11 | payload: spots,
12 | };
13 | };
14 |
15 | const getAllSpots = (spots) => {
16 | return {
17 | type: LOAD,
18 | payload: spots,
19 | };
20 | };
21 |
22 | const getAvailableSpots = (spots) => {
23 | return {
24 | type: LOAD_AVAILABLE,
25 | payload: spots,
26 | };
27 | };
28 |
29 | const createNewSpot = (spot) => {
30 | return {
31 | type: CREATE_SPOT,
32 | payload: spot,
33 | };
34 | };
35 |
36 | const deleteASpot = (spot) => {
37 | return {
38 | type: DELETE_SPOT,
39 | payload: spot,
40 | };
41 | };
42 |
43 | const getOneSpot = (spot) => {
44 | return {
45 | type: LOAD_ONE,
46 | payload: spot,
47 | };
48 | };
49 |
50 | export const getSpots = () => async (dispatch) => {
51 | const response = await fetch("/api/spots/");
52 | const spots = await response.json();
53 | return dispatch(getAllSpots(spots));
54 | };
55 |
56 | export const searchSpots = (guest_size, city) => async (dispatch) => {
57 | const response = await fetch(`/api/spots/search/${guest_size}&${city}`);
58 | const spots = await response.json();
59 | return dispatch(getAllSearchedSpots(spots));
60 | };
61 |
62 | export const getSpot = (id) => async (dispatch) => {
63 | const response = await fetch(`/api/spots/${id}`);
64 | const spot = await response.json();
65 | dispatch(getOneSpot(spot));
66 | return spot;
67 | };
68 |
69 | export const createSpot = ({
70 | image_url,
71 | title,
72 | address,
73 | city,
74 | state,
75 | zipcode,
76 | description,
77 | capacity,
78 | availability,
79 | latitude,
80 | longitude,
81 | }) => async (dispatch) => {
82 | const response = await fetch("/api/spots/", {
83 | method: "POST",
84 | headers: {
85 | "Content-Type": "application/json",
86 | },
87 | body: JSON.stringify({
88 | image_url,
89 | title,
90 | address,
91 | city,
92 | state,
93 | zipcode,
94 | description,
95 | capacity,
96 | availability,
97 | latitude,
98 | longitude,
99 | }),
100 | });
101 | const spot = await response.json();
102 | dispatch(createNewSpot(spot));
103 | return spot;
104 | };
105 |
106 | export const deleteSpot = ({ spotId }) => async (dispatch) => {
107 | const response = await fetch(`/api/spots/${spotId}`, {
108 | method: "DELETE",
109 | headers: {
110 | "Content-Type": "application/json",
111 | },
112 | });
113 | const spot = await response.json();
114 | return dispatch(deleteASpot(spot));
115 | };
116 |
117 | export const getTopAvailableSpots = () => async (dispatch) => {
118 | const response = await fetch("/api/spots/top-available");
119 | const spots = await response.json();
120 | return dispatch(getAvailableSpots(spots));
121 | };
122 |
123 | const initialState = {};
124 |
125 | const spotReducer = (state = initialState, action) => {
126 | let newState;
127 | switch (action.type) {
128 | case LOAD:
129 | newState = Object.assign({}, state, { ...action.payload });
130 | return newState;
131 | case LOAD_ONE:
132 | newState = Object.assign({}, state, { ...action.payload });
133 | return newState;
134 | case LOAD_AVAILABLE:
135 | newState = Object.assign({}, state, { ...action.payload });
136 | return newState;
137 | case LOAD_SEARCH:
138 | newState = Object.assign({}, state, { ...action.payload });
139 | return newState;
140 | case CREATE_SPOT:
141 | const new_spot = action.payload.spot;
142 | const all_spots = state.all_spots;
143 | newState = { all_spots: { ...all_spots, ...new_spot } };
144 | return newState;
145 | case DELETE_SPOT:
146 | const deleted_spot = action.payload.spot;
147 | newState = Object.assign({}, state);
148 | delete newState.all_spots[deleted_spot.id];
149 | return newState;
150 | default:
151 | return state;
152 | }
153 | };
154 |
155 | export default spotReducer;
156 |
--------------------------------------------------------------------------------
/react-app/src/components/SearchComponent/SearchBar/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { useDispatch, useSelector } from 'react-redux';
3 | import { useLocation } from 'react-router-dom';
4 |
5 | import InputGroup from 'react-bootstrap/InputGroup'
6 | import FormControl from 'react-bootstrap/FormControl'
7 | import Button from 'react-bootstrap/Button';
8 | import { ArrowRightCircleFill } from 'react-bootstrap-icons';
9 | import { Redirect, useHistory } from 'react-router-dom';
10 | import { getSpots, searchSpots } from '../../../store/spot';
11 |
12 | import './search-bar.css'
13 |
14 | const SearchBar = () => {
15 | const curr_location = useLocation()
16 | const dispatch = useDispatch()
17 | const history = useHistory()
18 | const [location, setLocation] = useState('');
19 | const [guestCount, setGuestCount] = useState(1)
20 |
21 | const spots = useSelector(state => state.spots.all_spots)
22 |
23 | const updateLocation = (event) => setLocation(event.target.value);
24 | const updateGuestCount = (event) => setGuestCount(event.target.value);
25 |
26 |
27 | useEffect(() => {
28 | dispatch(getSpots())
29 | }, [dispatch])
30 |
31 | const locationResults = (spots, location) => {
32 | for (let spotId in spots) {
33 | if (spots[spotId].city.toLowerCase().includes(location.toLowerCase())) {
34 | return ( setLocation(e.target.id)} id={`${spots[spotId].city}`}>{spots[spotId].city}
)
35 | }
36 | }
37 | }
38 |
39 | const search_bar = document.getElementById('search-bar-container-scrolled')
40 | const search_button = document.getElementById('search-butt')
41 |
42 | document.onscroll = () => {
43 | let top = window.pageYOffset
44 | if (top >= 75 && search_bar) {
45 | search_button.onmouseover = () => { search_button.style.color = '#c0c5c3' }
46 | search_button.onmouseout = () => { search_button.style.color = '#DAE2DF' }
47 | search_button.style.transition = "all .2s ease"
48 | search_bar.style.transition = "all .2s ease"
49 |
50 | } else if (top < 75 && search_bar) {
51 | search_button.onmouseover = () => { search_button.style.color = '#616060' }
52 | search_button.onmouseout = () => { search_button.style.color = '#6D696A' }
53 | }
54 | }
55 |
56 |
57 |
58 | const executeSearch = (e) => {
59 | dispatch(searchSpots(guestCount, location))
60 | return history.push(`/locate?city=${location}&guest=${guestCount}`)
61 | }
62 |
63 | useEffect(() => {
64 | document.getElementById("test-input").addEventListener("keyup", (e) => {
65 | e.preventDefault()
66 | if (e.keyCode === 13) {
67 | return executeSearch(e)
68 | }
69 | })
70 | document.getElementById("test-input2").addEventListener("keyup", (e) => {
71 | e.preventDefault()
72 | if (e.keyCode === 13) {
73 | return executeSearch(e)
74 | }
75 | })
76 | }, [location, guestCount])
77 |
78 | return (
79 |
107 | )
108 | }
109 |
110 | export default SearchBar
--------------------------------------------------------------------------------
/react-app/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { BrowserRouter, Route, Switch } from "react-router-dom";
3 | import { Provider as ReduxProvider } from "react-redux";
4 | import LoginModal from "./components/auth/LoginForm";
5 | import SignUpModal from "./components/auth/SignUpForm";
6 | import NavBar from "./components/NavBar";
7 | import ProtectedRoute from "./components/auth/ProtectedRoute";
8 | import UsersList from "./components/UsersList";
9 | import User from "./components/User";
10 | import { authenticate } from "./services/auth";
11 | import configureStore from "./store";
12 |
13 | import HomePage from "./components/HomePageComponent";
14 | import SpotPage from "./components/SpotComponent/SpotPage";
15 | import BookingPageComponent from "./components/BookingPageComponent";
16 | import FundingComponent from "./components/FundingComponent";
17 | import SearchPage from "./components/SearchComponent/SearchPage";
18 | import SpotCreate from "./components/SpotComponent/SpotCreate";
19 | import FooterComponent from "./components/FooterComponent"
20 |
21 | const store = configureStore();
22 |
23 | function App() {
24 | const [authenticated, setAuthenticated] = useState(false);
25 | const [loaded, setLoaded] = useState(false);
26 | const [showLogin, setShowLogin] = useState(false)
27 | const [showSignUp, setShowSignUp] = useState(false)
28 |
29 | useEffect(() => {
30 | (async () => {
31 | const user = await authenticate();
32 | if (!user.errors) {
33 | setAuthenticated(true);
34 | }
35 | setLoaded(true);
36 | })();
37 | }, []);
38 |
39 | if (!loaded) {
40 | return null;
41 | }
42 |
43 | return (
44 |
45 |
46 |
47 | setShowLogin(false)} authenticated={authenticated} setAuthenticated={setAuthenticated} setShowLogin={setShowLogin} setShowSignUp={setShowSignUp} />
48 | setShowSignUp(false)} authenticated={authenticated} setAuthenticated={setAuthenticated} setShowLogin={setShowLogin} setShowSignUp={setShowSignUp} />
49 |
50 |
56 |
57 |
58 |
64 |
65 |
66 |
67 | {/* Home Page: */}
68 |
69 |
70 |
71 |
72 | {/* testing spot create */}
73 |
79 |
80 |
81 |
82 | {/* Spot Page */}
83 |
89 |
90 |
91 |
92 | {/* Booking Page */}
93 |
99 |
100 |
101 |
102 | {/* Funding Page */}
103 |
109 |
110 |
111 |
112 | {/* Search Page */}
113 |
118 |
119 |
120 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | );
132 | }
133 |
134 | export default App;
135 |
--------------------------------------------------------------------------------
/react-app/src/components/auth/SignUpForm.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Redirect } from 'react-router-dom';
3 | import { signUp } from '../../services/auth';
4 | import { Modal, Button } from "react-bootstrap";
5 |
6 | // const SignUpForm = ({ authenticated, setAuthenticated }) => {
7 | // const [firstName, setFirstName] = useState("");
8 | // const [lastName, setLastName] = useState("");
9 | // const [email, setEmail] = useState("");
10 | // const [password, setPassword] = useState("");
11 | // const [repeatPassword, setRepeatPassword] = useState("");
12 |
13 | // const onSignUp = async (e) => {
14 | // e.preventDefault();
15 | // if (password === repeatPassword) {
16 | // const user = await signUp(firstName, lastName, email, password);
17 | // if (!user.errors) {
18 | // setAuthenticated(true);
19 | // }
20 | // }
21 | // };
22 |
23 | // const updateFirstName = (e) => {
24 | // setFirstName(e.target.value);
25 | // };
26 |
27 | // const updateLastName = (e) => {
28 | // setLastName(e.target.value);
29 | // };
30 |
31 | // const updateEmail = (e) => {
32 | // setEmail(e.target.value);
33 | // };
34 |
35 | // const updatePassword = (e) => {
36 | // setPassword(e.target.value);
37 | // };
38 |
39 | // const updateRepeatPassword = (e) => {
40 | // setRepeatPassword(e.target.value);
41 | // };
42 |
43 | // if (authenticated) {
44 | // return ;
45 | // }
46 |
47 | // return (
48 | //
97 | // );
98 | // };
99 |
100 | // export default SignUpForm;
101 |
102 | const SignUpModal = (props) => {
103 | const [firstName, setFirstName] = useState("");
104 | const [lastName, setLastName] = useState("");
105 | const [email, setEmail] = useState("");
106 | const [password, setPassword] = useState("");
107 | const [repeatPassword, setRepeatPassword] = useState("");
108 |
109 | const { authenticated, setAuthenticated } = props
110 |
111 | const switchToLogin = () => {
112 | props.setShowSignUp(false);
113 | props.setShowLogin(true);
114 | };
115 |
116 | const onSignUp = async (e) => {
117 | e.preventDefault();
118 | if (password === repeatPassword) {
119 | const user = await signUp(firstName, lastName, email, password);
120 | if (!user.errors) {
121 | setAuthenticated(true);
122 | }
123 | }
124 | };
125 |
126 | const updateFirstName = (e) => {
127 | setFirstName(e.target.value);
128 | };
129 |
130 | const updateLastName = (e) => {
131 | setLastName(e.target.value);
132 | };
133 |
134 | const updateEmail = (e) => {
135 | setEmail(e.target.value);
136 | };
137 |
138 | const updatePassword = (e) => {
139 | setPassword(e.target.value);
140 | };
141 |
142 | const updateRepeatPassword = (e) => {
143 | setRepeatPassword(e.target.value);
144 | };
145 |
146 | if (authenticated) {
147 | return ;
148 | }
149 |
150 |
151 | return (
152 |
158 |
159 |
160 | Sign Up
161 |
162 |
163 |
164 |
217 |
218 |
219 | Close
220 |
221 |
222 | );
223 | }
224 |
225 | export default SignUpModal
--------------------------------------------------------------------------------
/react-app/src/components/SpotComponent/SpotCreate/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import FormControl from "react-bootstrap/FormControl";
4 | import Button from "react-bootstrap/Button";
5 | import FormGroup from "react-bootstrap/FormGroup";
6 | import FormLabel from "react-bootstrap/FormLabel";
7 | import Form from "react-bootstrap/Form";
8 | import Row from "react-bootstrap/Row";
9 | import Col from "react-bootstrap/Col";
10 | import Container from "react-bootstrap/Container";
11 | import UploadPictureS3Client from "../../../aws/s3";
12 | import { createSpot, getTopAvailableSpots } from "../../../store/spot";
13 | import { useHistory } from "react-router-dom";
14 | import Geocode from "react-geocode";
15 | import { googleApiKey } from "../../GoogleMapsComponent/apikey";
16 |
17 | import "./SpotCreate.css";
18 |
19 | const SpotCreate = () => {
20 | const dispatch = useDispatch();
21 | const history = useHistory();
22 | const createdSpot = useSelector((state) => state.spots.all_spots);
23 |
24 | const states = [
25 | "AL",
26 | "AK",
27 | "AZ",
28 | "AR",
29 | "CA",
30 | "CO",
31 | "CT",
32 | "DE",
33 | "FL",
34 | "GA",
35 | "HI",
36 | "ID",
37 | "IL",
38 | "IN",
39 | "IA",
40 | "KS",
41 | "KY",
42 | "LA",
43 | "ME",
44 | "MD",
45 | "MA",
46 | "MI",
47 | "MN",
48 | "MS",
49 | "MO",
50 | "MT",
51 | "NE",
52 | "NV",
53 | "NH",
54 | "NJ",
55 | "NM",
56 | "NY",
57 | "NC",
58 | "ND",
59 | "OH",
60 | "OK",
61 | "OR",
62 | "PA",
63 | "RI",
64 | "SC",
65 | "SD",
66 | "TN",
67 | "TX",
68 | "UT",
69 | "VT",
70 | "VA",
71 | "WA",
72 | "WV",
73 | "WI",
74 | "WY",
75 | ];
76 |
77 | const [imageUrl, setImageUrl] = useState("");
78 | const [title, setTitle] = useState("");
79 | const [address, setAddress] = useState("");
80 | const [city, setCity] = useState("");
81 | const [state, setState] = useState(states[0]);
82 | const [zipcode, setZipcode] = useState("");
83 | const [description, setDescription] = useState("");
84 | const [capacity, setCapacity] = useState("");
85 |
86 | Geocode.setApiKey(googleApiKey);
87 | Geocode.setLanguage("en");
88 | Geocode.setLocationType("ROOFTOP");
89 |
90 | const getLat = (address, city, state, zipcode) => {
91 | return Geocode.fromAddress(`${address} ${city}, ${state} ${zipcode}`).then(
92 | (response) => {
93 | const { lat } = response.results[0].geometry.location;
94 | return lat;
95 | },
96 | (error) => {
97 | console.error(error);
98 | }
99 | );
100 | };
101 |
102 | const getLng = (address, city, state, zipcode) => {
103 | return Geocode.fromAddress(`${address} ${city}, ${state} ${zipcode}`).then(
104 | (response) => {
105 | const { lng } = response.results[0].geometry.location;
106 | return lng;
107 | },
108 | (error) => {
109 | console.error(error);
110 | }
111 | );
112 | };
113 |
114 | const handleSubmit = async (event) => {
115 | event.preventDefault();
116 | const lat = await getLat(address, city, state, zipcode);
117 | const lng = await getLng(address, city, state, zipcode);
118 | const newSpot = {
119 | image_url: imageUrl,
120 | title,
121 | address,
122 | city,
123 | state,
124 | zipcode,
125 | description,
126 | capacity,
127 | latitude: lat,
128 | longitude: lng,
129 | };
130 |
131 | let addedSpot = await dispatch(createSpot(newSpot));
132 |
133 | history.push(`/spot/${addedSpot.spot.id}`);
134 | };
135 |
136 | const uploadFile = (e) => {
137 | e.preventDefault();
138 |
139 | UploadPictureS3Client.uploadFile(
140 | e.target.files[0],
141 | `spot-${title}-${new Date()}`
142 | ).then((data) => setImageUrl(data.location));
143 | };
144 |
145 | return (
146 |
147 |
148 |
298 |
299 |
300 | );
301 | };
302 |
303 | export default SpotCreate;
304 |
--------------------------------------------------------------------------------
/app/seeds/spots.py:
--------------------------------------------------------------------------------
1 | from app.models import db, Spot
2 |
3 |
4 | def seed_spots():
5 |
6 | spot_1 = Spot(image_url='https://m.media-amazon.com/images/I/61cKLDstV3L._AC_SX522_.jpg', title="Gen's Orphanage", address='7567 Court Ave.',
7 | city='Oviedo', state='FL', zipcode=32765, description='Amazing 10 bedroom home.', capacity=20, availability=20, latitude=28.6700, longitude=81.2081, host_id=2)
8 |
9 | spot_2 = Spot(image_url='https://www.bostondesignguide.com/sites/default/files/architecturaldesign-summerhomes-1.jpg', title="Juliet's Summer Home", address='762 Old Summerhouse St.',
10 | city='Goodlettsville', state='TN', zipcode=37072, description='Beautiful summer home located in the Tennesse hills', capacity=4, availability=4, latitude=36.3231, longitude=-86.7133, host_id=3)
11 |
12 | spot_3 = Spot(image_url='https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/bojnice-castle-1603142898.jpg', title="Alfredo's Castle", address='84 Courtland St.',
13 | city='Fremont', state='OH', zipcode=43420, description='Stunning countryside castle ', capacity=500, availability=500, latitude=41.3503, longitude=-83.1219, host_id=4)
14 |
15 | spot_4 = Spot(image_url='https://images.mansionglobal.com/im-191825?width=1280?width=1280&size=1', title="Ed's Mansion", address='11 Pleasant Avenue',
16 | city='Fenton', state='MI', zipcode=48430, description='Amazing 10 bedroom home.', capacity=50, availability=50, latitude=42.7978, longitude=-83.7049, host_id=5)
17 |
18 | spot_5 = Spot(image_url='https://gambrick.com/wp-content/uploads/2019/05/small-house-colors-brick.jpg', title="Ranch style", address='7563 Fith Street',
19 | city='Fort Worth', state='TX', zipcode=76006, description='Ranch style home off busy street in Fort Worth', capacity=5, availability=5, latitude=32.7555, longitude=-97.3308, host_id=5)
20 |
21 | spot_6 = Spot(image_url='https://www.rocketmortgage.com/resources-cmsassets/RocketMortgage.com/Article_Images/Large_Images/TypesOfHomes/types-of-homes-hero.jpg', title="Cozy family home", address='5 Wellington St',
22 | city='Lubbock', state='TX', zipcode=79382, description='We are happy to host anyone in need of shelter.', capacity=1, availability=1, latitude=101.8313, longitude=-101.8552, host_id=6)
23 |
24 | spot_7 = Spot(image_url='https://static01.nyt.com/images/2019/06/25/realestate/25domestic-zeff/a1c1a1a36c9e4ff8adcb958c4276f28d-jumbo.jpg', title="All welcome here!", address='53 Mayfield Street',
25 | city='Amarillo', state='TX', zipcode=79101, description='Our home is open to anyone in need of shelter and a warm plate', capacity=3, availability=3, latitude=35.2220, longitude=-101.8313, host_id=7)
26 |
27 | spot_8 = Spot(image_url='https://static01.nyt.com/images/2019/06/25/realestate/25domestic-zeff/a1c1a1a36c9e4ff8adcb958c4276f28d-jumbo.jpg', title="Open to those in need!", address='368 Roosevelt Street',
28 | city='Oklahoma City', state='OK', zipcode=73008, description='We have room for five but can take more if willing to use sleeping bags', capacity=5, availability=5, latitude=35.4676, longitude=-97.5164, host_id=8)
29 |
30 | spot_9 = Spot(image_url='https://assets.themortgagereports.com/wp-content/uploads/2017/12/How-to-Buy-a-House-with-Low-Income-This-Year.jpg', title="A place to stay dry and warm!", address='9371 Foxrun Road',
31 | city='Midland', state='TX', zipcode=79701, description='When the weather is bad, we open our home for 10 people in need.', capacity=10, availability=10, latitude=31.9973, longitude=-102.0779, host_id=9)
32 |
33 | spot_10 = Spot(image_url='https://ei.marketwatch.com/Multimedia/2019/05/22/Photos/ZQ/MW-HK104_lillev_20190522122922_ZQ.jpg', title="Tudor style home", address='8351 S. Holly Avenue',
34 | city='Abilene', state='TX', zipcode=79601, description='', capacity=1, availability=1, latitude=32.4487, longitude=-99.7331, host_id=5)
35 |
36 | spot_11 = Spot(image_url='https://www.rocketmortgage.com/resources-cmsassets/RocketMortgage.com/Article_Images/Large_Images/Stock-Suburban-Home.jpg', title="Bunk-beds galore", address='8013C Boston Ave',
37 | city='San Angelo', state='TX', zipcode=76901, description='We have a room with two sets of bunk-beds that is great for kids', capacity=7, availability=7, latitude=31.4638, longitude=-100.4370, host_id=6)
38 |
39 | spot_12 = Spot(image_url='https://image.cnbcfm.com/api/v1/image/104559560-Jackson_Mississippi.jpg', title="Basement level, fully functional", address='9616 E. Kingston Dr',
40 | city='Wichita Falls', state='TX', zipcode=76301, description='Our home has a basement that is fully functional with a kitchen, bathroom, and two bedrooms.', capacity=8, availability=8, latitude=33.9137, longitude=-98.4934, host_id=7)
41 |
42 | spot_13 = Spot(image_url='https://charlotteaxios-charlotteagenda.netdna-ssl.com/wp-content/uploads/2019/07/July-open-houses-header-1.jpg', title="Safe Shelter", address='513 North Cambridge Ave',
43 | city='Waco', state='TX', zipcode=76633, description='Stay as long as you need!', capacity=5, availability=3, latitude=31.5493, longitude=-97.1467, host_id=8)
44 |
45 | spot_14 = Spot(image_url='https://media-cdn.wehco.com/img/photos/2019/09/06/190905foresthillhistorichomestour05b8900336972.jpg', title="One night only", address='63 Armstrong Court',
46 | city='Tyler', state='TX', zipcode=75701, description='We can host up to 2 guests for a max of one night at a time.', capacity=2, availability=2, latitude=32.3513, longitude=-95.3011, host_id=9)
47 |
48 | spot_15 = Spot(image_url='https://empire-s3-production.bobvila.com/slides/8507/original/putty_house.jpg', title="Safe neighborhood", address='23 Bay Meadows Dr',
49 | city='Paris', state='TX', zipcode=75460, description='Your average home in a safe neighborhood. Please dont hesitate to book with us!', capacity=3, availability=3, latitude=33.6609, longitude=-95.5555, host_id=5)
50 |
51 | spot_16 = Spot(image_url='https://cdn.houseplansservices.com/product/jbo1rrf0j7s4ebo15acfa89ar/w800x533.jpg', title="Room for your dogs!", address='79 S. Cedar Street',
52 | city='Guthrie', state='TX', zipcode=79236, description='We have a fenced in backyard so please bring your furry friends too!', capacity=3, availability=3, latitude=33.6207, longitude=-100.3228, host_id=6)
53 |
54 | spot_17 = Spot(image_url='https://photos.zillowstatic.com/fp/ef2b1d11bc5c7b1a33c59bebd4bf338b-p_e.jpg', title="Farm House", address='67 Shore Road',
55 | city='Fresno', state='CA', zipcode=93650, description='We have a farm house with a private guest house. Perfect for an entire family in need.', capacity=7, availability=5, latitude=36.7378, longitude=-119.7871, host_id=7)
56 |
57 | spot_18 = Spot(image_url='https://www.theday.com/storyimage/NL/20201105/NWS01/201109888/AR/0/AR-201109888.jpg', title="Garage Apartment", address='370 Arrowhead Street',
58 | city='Portland', state='OR', zipcode=97035, description='Stay dry in our apartment attached to our garage. You will have plenty of privacy', capacity=10, availability=10, latitude=45.5051, longitude=-122.6750, host_id=8)
59 |
60 | spot_19 = Spot(image_url='https://images.adsttc.com/media/images/5c2e/ec1d/08a5/e549/4e00/0045/large_jpg/Unjung-dong_brick_house_(1).jpg', title="Room for one", address='55 Evergreen Rd',
61 | city='Philadelphia', state='PA', zipcode=19019, description='We have a spare bedroom that can easily accommodate someone looking for shelter', capacity=1, availability=1, latitude=39.9526, longitude=-75.1652, host_id=9)
62 |
63 | spot_20 = Spot(image_url='https://media-cdn.wehco.com/img/photos/2014/09/21/LM-Monaco-home-13_t1070_ha8041694561a87fcc20f356e91cfcd09434b5e1b.jpg', title="Warm house, clean water, & internet", address='108 Rosewood Street',
64 | city='Jefferson City', state='MO', zipcode=65101, description='Whatever the reason, please dont hesitate to book a stay with us.', capacity=2, availability=1, latitude=38.5767, longitude=-92.1735, host_id=5)
65 |
66 | spot_21 = Spot(image_url='https://historicindianapolis.com/wp-content/uploads/2012/09/1531-Broadway-01.jpg', title="Well heated family home", address='91 Queen St',
67 | city='Syracuse', state='NY', zipcode=13201, description='When the neighborhood loses electricity you can count on us for help.', capacity=4, availability=2, latitude=43.0481, longitude=-76.1474, host_id=6)
68 |
69 | spot_22 = Spot(image_url='https://i.pinimg.com/originals/ba/b0/f8/bab0f85e67ab72a2d95995cf434ef506.jpg', title="Family home on river bluff", address='7 South Clinton St',
70 | city='Little Rock', state='AR', zipcode=72002, description='We would be more than happy to host anyone in need of food or a place to sleep.', capacity=8, availability=8, latitude=34.746, longitude=-92.2896, host_id=7)
71 |
72 | spot_23 = Spot(image_url='https://charlotteaxios-charlotteagenda.netdna-ssl.com/wp-content/uploads/2019/07/July-open-houses-header-1.jpg', title="Stay in the Shade", address='578 Colonial Ave',
73 | city='Tucson', state='AZ', zipcode=85641, description='When you are find yourself in an emergency, let us help and book with us', capacity=5, availability=3, latitude=32.2226, longitude=-110.9747, host_id=8)
74 |
75 | spot_24 = Spot(image_url='https://www.rocketmortgage.com/resources-cmsassets/RocketMortgage.com/Article_Images/Large_Images/Stock-Suburban-Home.jpg', title="Blizzard House", address='8341 Aspen Road',
76 | city='Denver', state='CO', zipcode=80014, description='Our house never loses power in a blizzard. Book with us to stay warm!', capacity=3, availability=3, latitude=39.7392, longitude=-104.9903, host_id=9)
77 |
78 | spot_25 = Spot(image_url='https://static01.nyt.com/images/2019/06/25/realestate/25domestic-zeff/a1c1a1a36c9e4ff8adcb958c4276f28d-jumbo.jpg', title="A break from the cold", address='976 Garfield Lane',
79 | city='Boise', state='ID', zipcode=83701, description='It can get cold in Boise. If you ever need to warm up, book with us!', capacity=1, availability=1, latitude=43.6150, longitude=-116.2023, host_id=5)
80 |
81 | db.session.add(spot_1)
82 | db.session.add(spot_2)
83 | db.session.add(spot_3)
84 | db.session.add(spot_5)
85 | db.session.add(spot_6)
86 | db.session.add(spot_7)
87 | db.session.add(spot_8)
88 | db.session.add(spot_9)
89 | db.session.add(spot_10)
90 | db.session.add(spot_11)
91 | db.session.add(spot_12)
92 | db.session.add(spot_13)
93 | db.session.add(spot_14)
94 | db.session.add(spot_15)
95 | db.session.add(spot_16)
96 | db.session.add(spot_17)
97 | db.session.add(spot_18)
98 | db.session.add(spot_19)
99 | db.session.add(spot_20)
100 | db.session.add(spot_21)
101 | db.session.add(spot_22)
102 | db.session.add(spot_23)
103 | db.session.add(spot_24)
104 | db.session.add(spot_25)
105 |
106 | db.session.commit()
107 |
108 | # Uses a raw SQL query to TRUNCATE the users table.
109 | # SQLAlchemy doesn't have a built in function to do this
110 | # TRUNCATE Removes all the data from the table, and resets
111 | # the auto incrementing primary key
112 |
113 |
114 | def undo_spots():
115 | Spot.query.delete()
116 | db.session.commit()
117 |
--------------------------------------------------------------------------------
/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "5c8e25a8a80e32d2d92baa84dd45978ac903380fdb4af3e0c53573583bbbc52d"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.8"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "alembic": {
20 | "hashes": [
21 | "sha256:4e02ed2aa796bd179965041afa092c55b51fb077de19d61835673cc80672c01c",
22 | "sha256:5334f32314fb2a56d86b4c4dd1ae34b08c03cae4cb888bc699942104d66bc245"
23 | ],
24 | "index": "pypi",
25 | "version": "==1.4.3"
26 | },
27 | "click": {
28 | "hashes": [
29 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
30 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
31 | ],
32 | "index": "pypi",
33 | "version": "==7.1.2"
34 | },
35 | "dnspython": {
36 | "hashes": [
37 | "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216",
38 | "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4"
39 | ],
40 | "markers": "python_version >= '3.6'",
41 | "version": "==2.1.0"
42 | },
43 | "email-validator": {
44 | "hashes": [
45 | "sha256:094b1d1c60d790649989d38d34f69e1ef07792366277a2cf88684d03495d018f",
46 | "sha256:1a13bd6050d1db4475f13e444e169b6fe872434922d38968c67cea9568cce2f0"
47 | ],
48 | "index": "pypi",
49 | "version": "==1.1.2"
50 | },
51 | "flask": {
52 | "hashes": [
53 | "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
54 | "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"
55 | ],
56 | "index": "pypi",
57 | "version": "==1.1.2"
58 | },
59 | "flask-cors": {
60 | "hashes": [
61 | "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16",
62 | "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"
63 | ],
64 | "index": "pypi",
65 | "version": "==3.0.8"
66 | },
67 | "flask-jwt-extended": {
68 | "hashes": [
69 | "sha256:0aa8ee6fa7eb3be9314e39dd199ac8e19389a95371f9d54e155c7aa635e319dd"
70 | ],
71 | "index": "pypi",
72 | "version": "==3.24.1"
73 | },
74 | "flask-login": {
75 | "hashes": [
76 | "sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b",
77 | "sha256:7451b5001e17837ba58945aead261ba425fdf7b4f0448777e597ddab39f4fba0"
78 | ],
79 | "index": "pypi",
80 | "version": "==0.5.0"
81 | },
82 | "flask-migrate": {
83 | "hashes": [
84 | "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732",
85 | "sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee"
86 | ],
87 | "index": "pypi",
88 | "version": "==2.5.3"
89 | },
90 | "flask-sqlalchemy": {
91 | "hashes": [
92 | "sha256:05b31d2034dd3f2a685cbbae4cfc4ed906b2a733cff7964ada450fd5e462b84e",
93 | "sha256:bfc7150eaf809b1c283879302f04c42791136060c6eeb12c0c6674fb1291fae5"
94 | ],
95 | "index": "pypi",
96 | "version": "==2.4.4"
97 | },
98 | "flask-wtf": {
99 | "hashes": [
100 | "sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2",
101 | "sha256:d417e3a0008b5ba583da1763e4db0f55a1269d9dd91dcc3eb3c026d3c5dbd720"
102 | ],
103 | "index": "pypi",
104 | "version": "==0.14.3"
105 | },
106 | "gunicorn": {
107 | "hashes": [
108 | "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626",
109 | "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"
110 | ],
111 | "index": "pypi",
112 | "version": "==20.0.4"
113 | },
114 | "idna": {
115 | "hashes": [
116 | "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16",
117 | "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1"
118 | ],
119 | "markers": "python_version >= '3.4'",
120 | "version": "==3.1"
121 | },
122 | "itsdangerous": {
123 | "hashes": [
124 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
125 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
126 | ],
127 | "index": "pypi",
128 | "version": "==1.1.0"
129 | },
130 | "jinja2": {
131 | "hashes": [
132 | "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
133 | "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
134 | ],
135 | "index": "pypi",
136 | "version": "==2.11.2"
137 | },
138 | "mako": {
139 | "hashes": [
140 | "sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27",
141 | "sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9"
142 | ],
143 | "index": "pypi",
144 | "version": "==1.1.3"
145 | },
146 | "markupsafe": {
147 | "hashes": [
148 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
149 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
150 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
151 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
152 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
153 | "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f",
154 | "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39",
155 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
156 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
157 | "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014",
158 | "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f",
159 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
160 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
161 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
162 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
163 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
164 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
165 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
166 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
167 | "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85",
168 | "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1",
169 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
170 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
171 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
172 | "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850",
173 | "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0",
174 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
175 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
176 | "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb",
177 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
178 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
179 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
180 | "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1",
181 | "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2",
182 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
183 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
184 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
185 | "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7",
186 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
187 | "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8",
188 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
189 | "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193",
190 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
191 | "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b",
192 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
193 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
194 | "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5",
195 | "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c",
196 | "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032",
197 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
198 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be",
199 | "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"
200 | ],
201 | "index": "pypi",
202 | "version": "==1.1.1"
203 | },
204 | "pyjwt": {
205 | "hashes": [
206 | "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e",
207 | "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"
208 | ],
209 | "index": "pypi",
210 | "version": "==1.7.1"
211 | },
212 | "python-dateutil": {
213 | "hashes": [
214 | "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
215 | "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
216 | ],
217 | "index": "pypi",
218 | "version": "==2.8.1"
219 | },
220 | "python-dotenv": {
221 | "hashes": [
222 | "sha256:8c10c99a1b25d9a68058a1ad6f90381a62ba68230ca93966882a4dbc3bc9c33d",
223 | "sha256:c10863aee750ad720f4f43436565e4c1698798d763b63234fb5021b6c616e423"
224 | ],
225 | "index": "pypi",
226 | "version": "==0.14.0"
227 | },
228 | "python-editor": {
229 | "hashes": [
230 | "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d",
231 | "sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b",
232 | "sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8",
233 | "sha256:c3da2053dbab6b29c94e43c486ff67206eafbe7eb52dbec7390b5e2fb05aac77",
234 | "sha256:ea87e17f6ec459e780e4221f295411462e0d0810858e055fc514684350a2f522"
235 | ],
236 | "index": "pypi",
237 | "version": "==1.0.4"
238 | },
239 | "six": {
240 | "hashes": [
241 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
242 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
243 | ],
244 | "index": "pypi",
245 | "version": "==1.15.0"
246 | },
247 | "sqlalchemy": {
248 | "hashes": [
249 | "sha256:072766c3bd09294d716b2d114d46ffc5ccf8ea0b714a4e1c48253014b771c6bb",
250 | "sha256:107d4af989831d7b091e382d192955679ec07a9209996bf8090f1f539ffc5804",
251 | "sha256:15c0bcd3c14f4086701c33a9e87e2c7ceb3bcb4a246cd88ec54a49cf2a5bd1a6",
252 | "sha256:26c5ca9d09f0e21b8671a32f7d83caad5be1f6ff45eef5ec2f6fd0db85fc5dc0",
253 | "sha256:276936d41111a501cf4a1a0543e25449108d87e9f8c94714f7660eaea89ae5fe",
254 | "sha256:3292a28344922415f939ee7f4fc0c186f3d5a0bf02192ceabd4f1129d71b08de",
255 | "sha256:33d29ae8f1dc7c75b191bb6833f55a19c932514b9b5ce8c3ab9bc3047da5db36",
256 | "sha256:3bba2e9fbedb0511769780fe1d63007081008c5c2d7d715e91858c94dbaa260e",
257 | "sha256:465c999ef30b1c7525f81330184121521418a67189053bcf585824d833c05b66",
258 | "sha256:51064ee7938526bab92acd049d41a1dc797422256086b39c08bafeffb9d304c6",
259 | "sha256:5a49e8473b1ab1228302ed27365ea0fadd4bf44bc0f9e73fe38e10fdd3d6b4fc",
260 | "sha256:618db68745682f64cedc96ca93707805d1f3a031747b5a0d8e150cfd5055ae4d",
261 | "sha256:6547b27698b5b3bbfc5210233bd9523de849b2bb8a0329cd754c9308fc8a05ce",
262 | "sha256:6557af9e0d23f46b8cd56f8af08eaac72d2e3c632ac8d5cf4e20215a8dca7cea",
263 | "sha256:73a40d4fcd35fdedce07b5885905753d5d4edf413fbe53544dd871f27d48bd4f",
264 | "sha256:8280f9dae4adb5889ce0bb3ec6a541bf05434db5f9ab7673078c00713d148365",
265 | "sha256:83469ad15262402b0e0974e612546bc0b05f379b5aa9072ebf66d0f8fef16bea",
266 | "sha256:860d0fe234922fd5552b7f807fbb039e3e7ca58c18c8d38aa0d0a95ddf4f6c23",
267 | "sha256:883c9fb62cebd1e7126dd683222b3b919657590c3e2db33bdc50ebbad53e0338",
268 | "sha256:8afcb6f4064d234a43fea108859942d9795c4060ed0fbd9082b0f280181a15c1",
269 | "sha256:96f51489ac187f4bab588cf51f9ff2d40b6d170ac9a4270ffaed535c8404256b",
270 | "sha256:9e865835e36dfbb1873b65e722ea627c096c11b05f796831e3a9b542926e979e",
271 | "sha256:aa0554495fe06172b550098909be8db79b5accdf6ffb59611900bea345df5eba",
272 | "sha256:b595e71c51657f9ee3235db8b53d0b57c09eee74dfb5b77edff0e46d2218dc02",
273 | "sha256:b6ff91356354b7ff3bd208adcf875056d3d886ed7cef90c571aef2ab8a554b12",
274 | "sha256:b70bad2f1a5bd3460746c3fb3ab69e4e0eb5f59d977a23f9b66e5bdc74d97b86",
275 | "sha256:c7adb1f69a80573698c2def5ead584138ca00fff4ad9785a4b0b2bf927ba308d",
276 | "sha256:c898b3ebcc9eae7b36bd0b4bbbafce2d8076680f6868bcbacee2d39a7a9726a7",
277 | "sha256:e49947d583fe4d29af528677e4f0aa21f5e535ca2ae69c48270ebebd0d8843c0",
278 | "sha256:eb1d71643e4154398b02e88a42fc8b29db8c44ce4134cf0f4474bfc5cb5d4dac",
279 | "sha256:f2e8a9c0c8813a468aa659a01af6592f71cd30237ec27c4cc0683f089f90dcfc",
280 | "sha256:fe7fe11019fc3e6600819775a7d55abc5446dda07e9795f5954fdbf8a49e1c37"
281 | ],
282 | "index": "pypi",
283 | "version": "==1.3.19"
284 | },
285 | "werkzeug": {
286 | "hashes": [
287 | "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
288 | "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"
289 | ],
290 | "index": "pypi",
291 | "version": "==1.0.1"
292 | },
293 | "wtforms": {
294 | "hashes": [
295 | "sha256:7b504fc724d0d1d4d5d5c114e778ec88c37ea53144683e084215eed5155ada4c",
296 | "sha256:81195de0ac94fbc8368abbaf9197b88c4f3ffd6c2719b5bf5fc9da744f3d829c"
297 | ],
298 | "index": "pypi",
299 | "version": "==2.3.3"
300 | }
301 | },
302 | "develop": {
303 | "astroid": {
304 | "hashes": [
305 | "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703",
306 | "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"
307 | ],
308 | "markers": "python_version >= '3.5'",
309 | "version": "==2.4.2"
310 | },
311 | "autopep8": {
312 | "hashes": [
313 | "sha256:9e136c472c475f4ee4978b51a88a494bfcd4e3ed17950a44a988d9e434837bea",
314 | "sha256:cae4bc0fb616408191af41d062d7ec7ef8679c7f27b068875ca3a9e2878d5443"
315 | ],
316 | "index": "pypi",
317 | "version": "==1.5.5"
318 | },
319 | "isort": {
320 | "hashes": [
321 | "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e",
322 | "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"
323 | ],
324 | "markers": "python_version >= '3.6' and python_version < '4.0'",
325 | "version": "==5.7.0"
326 | },
327 | "lazy-object-proxy": {
328 | "hashes": [
329 | "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d",
330 | "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449",
331 | "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08",
332 | "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a",
333 | "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50",
334 | "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd",
335 | "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239",
336 | "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb",
337 | "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea",
338 | "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e",
339 | "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156",
340 | "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142",
341 | "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442",
342 | "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62",
343 | "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db",
344 | "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531",
345 | "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383",
346 | "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a",
347 | "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357",
348 | "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4",
349 | "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"
350 | ],
351 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
352 | "version": "==1.4.3"
353 | },
354 | "mccabe": {
355 | "hashes": [
356 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
357 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
358 | ],
359 | "version": "==0.6.1"
360 | },
361 | "psycopg2-binary": {
362 | "hashes": [
363 | "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c",
364 | "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67",
365 | "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0",
366 | "sha256:15978a1fbd225583dd8cdaf37e67ccc278b5abecb4caf6b2d6b8e2b948e953f6",
367 | "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db",
368 | "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94",
369 | "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52",
370 | "sha256:6422f2ff0919fd720195f64ffd8f924c1395d30f9a495f31e2392c2efafb5056",
371 | "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b",
372 | "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd",
373 | "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550",
374 | "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679",
375 | "sha256:89705f45ce07b2dfa806ee84439ec67c5d9a0ef20154e0e475e2b2ed392a5b83",
376 | "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77",
377 | "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2",
378 | "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77",
379 | "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2",
380 | "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd",
381 | "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859",
382 | "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1",
383 | "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25",
384 | "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152",
385 | "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf",
386 | "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f",
387 | "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729",
388 | "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71",
389 | "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66",
390 | "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4",
391 | "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449",
392 | "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da",
393 | "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a",
394 | "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c",
395 | "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb",
396 | "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4",
397 | "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5"
398 | ],
399 | "index": "pypi",
400 | "version": "==2.8.6"
401 | },
402 | "pycodestyle": {
403 | "hashes": [
404 | "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367",
405 | "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"
406 | ],
407 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
408 | "version": "==2.6.0"
409 | },
410 | "pylint": {
411 | "hashes": [
412 | "sha256:718b74786ea7ed07aa0c58bf572154d4679f960d26e9641cc1de204a30b87fc9",
413 | "sha256:e71c2e9614a4f06e36498f310027942b0f4f2fde20aebb01655b31edc63b9eaf"
414 | ],
415 | "index": "pypi",
416 | "version": "==2.6.2"
417 | },
418 | "six": {
419 | "hashes": [
420 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
421 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
422 | ],
423 | "index": "pypi",
424 | "version": "==1.15.0"
425 | },
426 | "toml": {
427 | "hashes": [
428 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
429 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
430 | ],
431 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
432 | "version": "==0.10.2"
433 | },
434 | "wrapt": {
435 | "hashes": [
436 | "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"
437 | ],
438 | "version": "==1.12.1"
439 | }
440 | }
441 | }
442 |
--------------------------------------------------------------------------------