├── .gitignore ├── server ├── migrations │ ├── README │ ├── script.py.mako │ ├── versions │ │ ├── c93a14d1aa19_create_table_birds.py │ │ ├── 2739c2b577b9_add_column_image.py │ │ └── 6034d52c059f_create_table_birds.py │ ├── alembic.ini │ └── env.py ├── models.py ├── seed.py └── app.py ├── client ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── images │ │ ├── dove.jpeg │ │ ├── grackle.jpeg │ │ ├── mallard.jpeg │ │ ├── starling.jpeg │ │ ├── grey-parrot.jpeg │ │ ├── canada-goose.jpeg │ │ └── black-capped-chickadee.jpeg │ ├── manifest.json │ └── index.html ├── src │ ├── components │ │ ├── Header.js │ │ ├── BirdList.js │ │ ├── BirdCard.js │ │ ├── Search.js │ │ ├── App.js │ │ ├── BirdDetail.js │ │ ├── BirdPage.js │ │ └── NewBirdForm.js │ ├── index.js │ └── index.css ├── .gitignore └── package.json ├── Procfile.dev ├── Pipfile ├── requirements.txt └── Pipfile.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | server/__pycache__ -------------------------------------------------------------------------------- /server/migrations/README: -------------------------------------------------------------------------------- 1 | Single-database configuration for Flask. 2 | -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: PORT=4000 npm start --prefix client 2 | api: gunicorn -b 127.0.0.1:5555 --chdir ./server app:app -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawn0123/python-p4-deployment-bird-app/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawn0123/python-p4-deployment-bird-app/HEAD/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawn0123/python-p4-deployment-bird-app/HEAD/client/public/logo512.png -------------------------------------------------------------------------------- /client/public/images/dove.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawn0123/python-p4-deployment-bird-app/HEAD/client/public/images/dove.jpeg -------------------------------------------------------------------------------- /client/public/images/grackle.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawn0123/python-p4-deployment-bird-app/HEAD/client/public/images/grackle.jpeg -------------------------------------------------------------------------------- /client/public/images/mallard.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawn0123/python-p4-deployment-bird-app/HEAD/client/public/images/mallard.jpeg -------------------------------------------------------------------------------- /client/public/images/starling.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawn0123/python-p4-deployment-bird-app/HEAD/client/public/images/starling.jpeg -------------------------------------------------------------------------------- /client/public/images/grey-parrot.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawn0123/python-p4-deployment-bird-app/HEAD/client/public/images/grey-parrot.jpeg -------------------------------------------------------------------------------- /client/public/images/canada-goose.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawn0123/python-p4-deployment-bird-app/HEAD/client/public/images/canada-goose.jpeg -------------------------------------------------------------------------------- /client/public/images/black-capped-chickadee.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawn0123/python-p4-deployment-bird-app/HEAD/client/public/images/black-capped-chickadee.jpeg -------------------------------------------------------------------------------- /client/src/components/Header.js: -------------------------------------------------------------------------------- 1 | function Header() { 2 | return ( 3 |
4 |

5 | Birdsy 6 | 7 | 🕊 8 | 9 |

10 |
11 | ); 12 | } 13 | 14 | export default Header; 15 | -------------------------------------------------------------------------------- /client/src/components/BirdList.js: -------------------------------------------------------------------------------- 1 | import BirdCard from "./BirdCard"; 2 | 3 | function BirdList({ birds }) { 4 | 5 | return ( 6 | 11 | ); 12 | } 13 | 14 | export default BirdList; 15 | -------------------------------------------------------------------------------- /client/src/components/BirdCard.js: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | function BirdCard({ bird }) { 4 | const { id, name, image } = bird; 5 | 6 | return ( 7 |
  • 8 | 9 | {name} 10 | 11 |

    {name}

    12 |
  • 13 | ); 14 | } 15 | 16 | export default BirdCard; 17 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./components/App"; 5 | import { BrowserRouter } from "react-router-dom"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | 12 | , 13 | document.getElementById("root") 14 | ); 15 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | flask = "*" 8 | flask-migrate = "*" 9 | flask-restful = "*" 10 | flask-sqlalchemy = "==2.5.1" 11 | gunicorn = "*" 12 | honcho = "*" 13 | importlib-metadata = "*" 14 | psycopg2-binary = "*" 15 | python-dotenv = "*" 16 | sqlalchemy-serializer = "*" 17 | flask-cors = "*" 18 | 19 | [dev-packages] 20 | 21 | [requires] 22 | python_version = "3.8.13" 23 | -------------------------------------------------------------------------------- /client/src/components/Search.js: -------------------------------------------------------------------------------- 1 | function Search({ searchTerm, onSearchChange }) { 2 | return ( 3 |
    4 | 5 | onSearchChange(e.target.value)} 11 | /> 12 |
    13 | ); 14 | } 15 | 16 | export default Search; 17 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .eslintcache 26 | 27 | server/migrations/ver -------------------------------------------------------------------------------- /server/models.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | from sqlalchemy_serializer import SerializerMixin 3 | 4 | db = SQLAlchemy() 5 | 6 | class Bird(db.Model, SerializerMixin): 7 | __tablename__ = 'birds' 8 | 9 | id = db.Column(db.Integer, primary_key=True) 10 | name = db.Column(db.String) 11 | species = db.Column(db.String) 12 | image = db.Column(db.String) 13 | 14 | def __repr__(self): 15 | return f'' 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -i https://pypi.org/simple 2 | alembic==1.8.1 3 | aniso8601==9.0.1 4 | click==8.1.3 5 | flask==2.2.2 6 | flask-cors==3.0.10 7 | flask-migrate==3.1.0 8 | flask-restful==0.3.9 9 | flask-sqlalchemy==3.0.2 10 | gunicorn==20.1.0 11 | honcho==1.1.0 12 | importlib-metadata==5.0.0 13 | itsdangerous==2.1.2 14 | jinja2==3.1.2 15 | mako==1.2.3 16 | markupsafe==2.1.1 17 | psycopg2-binary==2.9.4 18 | python-dotenv==0.21.0 19 | pytz==2022.4 20 | setuptools==65.5.0 21 | six==1.16.0 22 | sqlalchemy==1.4.42 23 | sqlalchemy-serializer==1.4.1 24 | werkzeug==2.2.2 25 | zipp==3.9.0 26 | -------------------------------------------------------------------------------- /client/src/components/App.js: -------------------------------------------------------------------------------- 1 | import { Routes, Route } from "react-router-dom"; 2 | import Header from "./Header"; 3 | import BirdPage from "./BirdPage"; 4 | import BirdDetail from "./BirdDetail" 5 | 6 | function App() { 7 | 8 | 9 | return ( 10 | <> 11 |
    12 |
    13 | 14 | }/> 15 | }/> 16 | 17 |
    18 | 19 | ); 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /server/migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /server/migrations/versions/c93a14d1aa19_create_table_birds.py: -------------------------------------------------------------------------------- 1 | """Create table birds 2 | 3 | Revision ID: c93a14d1aa19 4 | Revises: 2739c2b577b9 5 | Create Date: 2022-10-21 11:05:23.587383 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = 'c93a14d1aa19' 14 | down_revision = '2739c2b577b9' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | pass 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | pass 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /server/migrations/versions/2739c2b577b9_add_column_image.py: -------------------------------------------------------------------------------- 1 | """add column image 2 | 3 | Revision ID: 2739c2b577b9 4 | Revises: 6034d52c059f 5 | Create Date: 2022-10-17 10:10:53.451432 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '2739c2b577b9' 14 | down_revision = '6034d52c059f' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('birds', sa.Column('image', sa.String(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('birds', 'image') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "birdsy-client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "proxy": "http://127.0.0.1:5555", 6 | "dependencies": { 7 | "browser-router": "^0.2.0", 8 | "react": "^17.0.2", 9 | "react-dom": "^17.0.2", 10 | "react-router-dom": "^6.4.2", 11 | "react-scripts": "^5.0.1", 12 | "switch": "^0.0.0" 13 | }, 14 | "scripts": { 15 | "build": "react-scripts build", 16 | "start": "react-scripts start" 17 | }, 18 | "eslintConfig": { 19 | "extends": [ 20 | "react-app", 21 | "react-app/jest" 22 | ] 23 | }, 24 | "browserslist": { 25 | "production": [ 26 | ">0.2%", 27 | "not dead", 28 | "not op_mini all" 29 | ], 30 | "development": [ 31 | "last 1 chrome version", 32 | "last 1 firefox version", 33 | "last 1 safari version" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/migrations/versions/6034d52c059f_create_table_birds.py: -------------------------------------------------------------------------------- 1 | """create table birds 2 | 3 | Revision ID: 6034d52c059f 4 | Revises: 5 | Create Date: 2022-10-13 16:02:56.583047 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '6034d52c059f' 14 | down_revision = None 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('birds', 22 | sa.Column('id', sa.Integer(), nullable=False), 23 | sa.Column('name', sa.String(), nullable=True), 24 | sa.Column('species', sa.String(), nullable=True), 25 | sa.PrimaryKeyConstraint('id') 26 | ) 27 | # ### end Alembic commands ### 28 | 29 | 30 | def downgrade(): 31 | # ### commands auto generated by Alembic - please adjust! ### 32 | op.drop_table('birds') 33 | # ### end Alembic commands ### 34 | -------------------------------------------------------------------------------- /server/migrations/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | 11 | 12 | # Logging configuration 13 | [loggers] 14 | keys = root,sqlalchemy,alembic,flask_migrate 15 | 16 | [handlers] 17 | keys = console 18 | 19 | [formatters] 20 | keys = generic 21 | 22 | [logger_root] 23 | level = WARN 24 | handlers = console 25 | qualname = 26 | 27 | [logger_sqlalchemy] 28 | level = WARN 29 | handlers = 30 | qualname = sqlalchemy.engine 31 | 32 | [logger_alembic] 33 | level = INFO 34 | handlers = 35 | qualname = alembic 36 | 37 | [logger_flask_migrate] 38 | level = INFO 39 | handlers = 40 | qualname = flask_migrate 41 | 42 | [handler_console] 43 | class = StreamHandler 44 | args = (sys.stderr,) 45 | level = NOTSET 46 | formatter = generic 47 | 48 | [formatter_generic] 49 | format = %(levelname)-5.5s [%(name)s] %(message)s 50 | datefmt = %H:%M:%S 51 | -------------------------------------------------------------------------------- /server/seed.py: -------------------------------------------------------------------------------- 1 | from app import app 2 | from models import db, Bird 3 | 4 | db.init_app(app) 5 | 6 | with app.app_context(): 7 | 8 | print('Deleting existing birds...') 9 | Bird.query.delete() 10 | 11 | print('Creating bird objects...') 12 | chickadee = Bird( 13 | name='Black-Capped Chickadee', 14 | species='Poecile Atricapillus', 15 | image='/images/black-capped-chickadee.jpeg' 16 | ) 17 | grackle = Bird( 18 | name='Grackle', 19 | species='Quiscalus Quiscula', 20 | image='/images/grackle.jpeg' 21 | ) 22 | starling = Bird( 23 | name='Common Starling', 24 | species='Sturnus Vulgaris', 25 | image='/images/starling.jpeg' 26 | ) 27 | dove = Bird( 28 | name='Mourning Dove', 29 | species='Zenaida Macroura', 30 | image='/images/dove.jpeg' 31 | ) 32 | 33 | print('Adding bird objects to transaction...') 34 | db.session.add_all([chickadee, grackle, starling, dove]) 35 | print('Committing transaction...') 36 | db.session.commit() 37 | print('Complete.') 38 | -------------------------------------------------------------------------------- /client/src/components/BirdDetail.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useParams } from "react-router-dom"; 3 | 4 | function BirdDetail() { 5 | let { id } = useParams(); 6 | const [bird, setBird] = useState({}); 7 | 8 | useEffect(() => { 9 | fetch(`/birds/${id}`) 10 | .then((r) => r.json()) 11 | .then((bird) => setBird(bird)); 12 | 13 | return () => setBird({}); 14 | }, [id]); 15 | 16 | const { name, species, image } = bird; 17 | 18 | const [isInStock, setIsInStock] = useState(true); 19 | 20 | function handleToggleStock() { 21 | setIsInStock((isInStock) => !isInStock); 22 | } 23 | 24 | return ( 25 |
    26 | {name} 27 | 28 |

    {name}

    29 |

    Species: {species}

    30 | {isInStock ? ( 31 | 34 | ) : ( 35 | 36 | )} 37 |
    38 | ); 39 | } 40 | 41 | export default BirdDetail; 42 | -------------------------------------------------------------------------------- /client/src/components/BirdPage.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import NewBirdForm from "./NewBirdForm"; 3 | import BirdList from "./BirdList"; 4 | import Search from "./Search"; 5 | 6 | function BirdPage() { 7 | const [birds, setBirds] = useState([]); 8 | const [searchTerm, setSearchTerm] = useState(""); 9 | 10 | useEffect(() => { 11 | fetch("/birds") 12 | .then((r) => r.json()) 13 | .then((birdsArray) => { 14 | setBirds(birdsArray); 15 | }); 16 | }, []); 17 | 18 | function handleAddBird(newBird) { 19 | const updatedBirdsArray = [...birds, newBird]; 20 | setBirds(updatedBirdsArray); 21 | } 22 | 23 | const displayedBirds = birds.filter((bird) => { 24 | return bird.name.toLowerCase().includes(searchTerm.toLowerCase()); 25 | }); 26 | 27 | if(!birds) return

    ...loading

    28 | 29 | return ( 30 |
    31 | 32 | 33 | 34 |
    35 | ); 36 | } 37 | 38 | export default BirdPage; 39 | -------------------------------------------------------------------------------- /client/src/components/NewBirdForm.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | function NewBirdForm({ onAddBird }) { 4 | const [name, setName] = useState(""); 5 | const [species, setSpecies] = useState(""); 6 | const [image, setImage] = useState(""); 7 | 8 | function handleSubmit(e) { 9 | e.preventDefault(); 10 | fetch("/birds", { 11 | method: "POST", 12 | headers: { 13 | "Content-Type": "application/json", 14 | }, 15 | body: JSON.stringify({ 16 | name: name, 17 | species: species, 18 | image: image, 19 | }), 20 | }) 21 | .then((r) => r.json()) 22 | .then((newBird) => onAddBird(newBird)); 23 | } 24 | 25 | return ( 26 |
    27 |

    New Bird

    28 |
    29 | setName(e.target.value)} 35 | /> 36 | setSpecies(e.target.value)} 42 | /> 43 | setImage(e.target.value)} 49 | /> 50 | 51 |
    52 |
    53 | ); 54 | } 55 | 56 | export default NewBirdForm; 57 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
    32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /server/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | load_dotenv() 5 | 6 | from flask import Flask, jsonify, request, make_response, render_template 7 | from flask_migrate import Migrate 8 | from flask_restful import Api, Resource 9 | 10 | from models import db, Bird 11 | 12 | app = Flask( 13 | __name__, 14 | static_url_path='', 15 | static_folder='../client/build', 16 | template_folder='../client/build' 17 | ) 18 | 19 | app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get('DATABASE_URI') 20 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 21 | app.json.compact = False 22 | 23 | migrate = Migrate(app, db) 24 | db.init_app(app) 25 | 26 | @app.errorhandler(404) 27 | def not_found(e): 28 | return render_template("index.html") 29 | 30 | api = Api(app) 31 | 32 | class Birds(Resource): 33 | 34 | def get(self): 35 | birds = [bird.to_dict() for bird in Bird.query.all()] 36 | return make_response(jsonify(birds), 200) 37 | 38 | def post(self): 39 | 40 | data = request.get_json() 41 | 42 | new_bird = Bird( 43 | name=data['name'], 44 | species=data['species'], 45 | image=data['image'], 46 | ) 47 | 48 | db.session.add(new_bird) 49 | db.session.commit() 50 | 51 | return make_response(new_bird.to_dict(), 201) 52 | 53 | api.add_resource(Birds, '/birds') 54 | 55 | class BirdByID(Resource): 56 | 57 | def get(self, id): 58 | bird = Bird.query.filter_by(id=id).first().to_dict() 59 | return make_response(jsonify(bird), 200) 60 | 61 | def patch(self, id): 62 | 63 | data = request.get_json() 64 | 65 | bird = Bird.query.filter_by(id=id).first() 66 | 67 | for attr in data: 68 | setattr(bird, attr, data[attr]) 69 | 70 | db.session.add(bird) 71 | db.session.commit() 72 | 73 | return make_response(bird.to_dict(), 200) 74 | 75 | def delete(self, id): 76 | 77 | bird = Bird.query.filter_by(id=id).first() 78 | db.session.delete(bird) 79 | db.session.commit() 80 | 81 | return make_response('', 204) 82 | 83 | api.add_resource(BirdByID, '/birds/') -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --green: rgb(39, 189, 39); 3 | } 4 | 5 | * { 6 | margin: 0; 7 | padding: 0; 8 | box-sizing: border-box; 9 | } 10 | 11 | body { 12 | font-family: "Playfair Display", serif; 13 | } 14 | 15 | h2 { 16 | margin-bottom: 1rem; 17 | } 18 | 19 | button { 20 | padding: 0.25rem 1rem; 21 | font-family: inherit; 22 | border: none; 23 | cursor: pointer; 24 | } 25 | 26 | button.primary { 27 | background-color: var(--green); 28 | color: white; 29 | } 30 | 31 | .app { 32 | margin: 0 auto; 33 | max-width: 1200px; 34 | } 35 | 36 | header h1 { 37 | font-size: 2.5em; 38 | font-weight: 900; 39 | text-align: center; 40 | padding: 0.5rem 0; 41 | } 42 | 43 | .logo { 44 | font-size: 1.25em; 45 | padding-left: 0.5rem; 46 | -webkit-text-stroke: 2px #000000; 47 | } 48 | 49 | main { 50 | padding: 0 1rem; 51 | } 52 | 53 | .new-bird-form { 54 | padding: 2rem 1rem; 55 | margin: 2rem 0; 56 | background-color: rgba(181, 250, 181, 0.5); 57 | } 58 | 59 | .new-bird-form form { 60 | display: flex; 61 | justify-content: center; 62 | } 63 | 64 | .new-bird-form input { 65 | margin-right: 2rem; 66 | flex: 1; 67 | } 68 | 69 | .new-bird-form button { 70 | width: 150px; 71 | } 72 | 73 | .searchbar { 74 | padding: 1rem; 75 | display: flex; 76 | flex-direction: column; 77 | } 78 | 79 | label { 80 | font-weight: bold; 81 | display: block; 82 | } 83 | 84 | input { 85 | padding: 0.25rem; 86 | border: none; 87 | font-family: inherit; 88 | font-size: 1.2em; 89 | border-bottom: 2px solid gray; 90 | transition: all 0.2s; 91 | width: 100%; 92 | } 93 | 94 | input:focus { 95 | outline: none; 96 | border-bottom: 2px solid var(--green); 97 | } 98 | 99 | .cards { 100 | list-style: none; 101 | } 102 | 103 | .card, .details { 104 | margin: 1rem; 105 | border: 1px solid black; 106 | padding: 10px; 107 | } 108 | 109 | .card img { 110 | width: 100%; 111 | } 112 | 113 | .card button { 114 | margin-top: 0.5rem; 115 | } 116 | 117 | .details { 118 | max-width: 50%; 119 | text-align: center; 120 | margin-left: auto; 121 | margin-right: auto; 122 | height: auto; 123 | } 124 | 125 | .details img { 126 | max-width: 85%; 127 | } 128 | 129 | @media (min-width: 768px) { 130 | .cards { 131 | display: flex; 132 | flex-wrap: wrap; 133 | } 134 | 135 | .card { 136 | width: calc(33% - 2rem); 137 | } 138 | } 139 | 140 | @media (min-width: 1024px) { 141 | .card { 142 | width: calc(25% - 2rem); 143 | } 144 | } 145 | 146 | @media (min-width: 1200px) { 147 | .card { 148 | width: calc(20% - 2rem); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /server/migrations/env.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | 3 | import logging 4 | from logging.config import fileConfig 5 | 6 | from flask import current_app 7 | 8 | from alembic import context 9 | 10 | # this is the Alembic Config object, which provides 11 | # access to the values within the .ini file in use. 12 | config = context.config 13 | 14 | # Interpret the config file for Python logging. 15 | # This line sets up loggers basically. 16 | fileConfig(config.config_file_name) 17 | logger = logging.getLogger('alembic.env') 18 | 19 | # add your model's MetaData object here 20 | # for 'autogenerate' support 21 | # from myapp import mymodel 22 | # target_metadata = mymodel.Base.metadata 23 | config.set_main_option( 24 | 'sqlalchemy.url', 25 | str(current_app.extensions['migrate'].db.get_engine().url).replace( 26 | '%', '%%')) 27 | target_metadata = current_app.extensions['migrate'].db.metadata 28 | 29 | # other values from the config, defined by the needs of env.py, 30 | # can be acquired: 31 | # my_important_option = config.get_main_option("my_important_option") 32 | # ... etc. 33 | 34 | 35 | def run_migrations_offline(): 36 | """Run migrations in 'offline' mode. 37 | 38 | This configures the context with just a URL 39 | and not an Engine, though an Engine is acceptable 40 | here as well. By skipping the Engine creation 41 | we don't even need a DBAPI to be available. 42 | 43 | Calls to context.execute() here emit the given string to the 44 | script output. 45 | 46 | """ 47 | url = config.get_main_option("sqlalchemy.url") 48 | context.configure( 49 | url=url, target_metadata=target_metadata, literal_binds=True 50 | ) 51 | 52 | with context.begin_transaction(): 53 | context.run_migrations() 54 | 55 | 56 | def run_migrations_online(): 57 | """Run migrations in 'online' mode. 58 | 59 | In this scenario we need to create an Engine 60 | and associate a connection with the context. 61 | 62 | """ 63 | 64 | # this callback is used to prevent an auto-migration from being generated 65 | # when there are no changes to the schema 66 | # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html 67 | def process_revision_directives(context, revision, directives): 68 | if getattr(config.cmd_opts, 'autogenerate', False): 69 | script = directives[0] 70 | if script.upgrade_ops.is_empty(): 71 | directives[:] = [] 72 | logger.info('No changes in schema detected.') 73 | 74 | connectable = current_app.extensions['migrate'].db.get_engine() 75 | 76 | with connectable.connect() as connection: 77 | context.configure( 78 | connection=connection, 79 | target_metadata=target_metadata, 80 | process_revision_directives=process_revision_directives, 81 | **current_app.extensions['migrate'].configure_args 82 | ) 83 | 84 | with context.begin_transaction(): 85 | context.run_migrations() 86 | 87 | 88 | if context.is_offline_mode(): 89 | run_migrations_offline() 90 | else: 91 | run_migrations_online() 92 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "c7e7fb8a92fd3096ff042c487d2fcf4086ff266e36914651bf8e4a9061ccc032" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8.13" 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:0a024d7f2de88d738d7395ff866997314c837be6104e90c5724350313dee4da4", 22 | "sha256:cd0b5e45b14b706426b833f06369b9a6d5ee03f826ec3238723ce8caaf6e5ffa" 23 | ], 24 | "markers": "python_version >= '3.7'", 25 | "version": "==1.8.1" 26 | }, 27 | "aniso8601": { 28 | "hashes": [ 29 | "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f", 30 | "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973" 31 | ], 32 | "version": "==9.0.1" 33 | }, 34 | "click": { 35 | "hashes": [ 36 | "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", 37 | "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" 38 | ], 39 | "markers": "python_version >= '3.7'", 40 | "version": "==8.1.3" 41 | }, 42 | "flask": { 43 | "hashes": [ 44 | "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", 45 | "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" 46 | ], 47 | "index": "pypi", 48 | "version": "==2.2.2" 49 | }, 50 | "flask-cors": { 51 | "hashes": [ 52 | "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438", 53 | "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de" 54 | ], 55 | "index": "pypi", 56 | "version": "==3.0.10" 57 | }, 58 | "flask-migrate": { 59 | "hashes": [ 60 | "sha256:57d6060839e3a7f150eaab6fe4e726d9e3e7cffe2150fb223d73f92421c6d1d9", 61 | "sha256:a6498706241aba6be7a251078de9cf166d74307bca41a4ca3e403c9d39e2f897" 62 | ], 63 | "index": "pypi", 64 | "version": "==3.1.0" 65 | }, 66 | "flask-restful": { 67 | "hashes": [ 68 | "sha256:4970c49b6488e46c520b325f54833374dc2b98e211f1b272bd4b0c516232afe2", 69 | "sha256:ccec650b835d48192138c85329ae03735e6ced58e9b2d9c2146d6c84c06fa53e" 70 | ], 71 | "index": "pypi", 72 | "version": "==0.3.9" 73 | }, 74 | "flask-sqlalchemy": { 75 | "hashes": [ 76 | "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912", 77 | "sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390" 78 | ], 79 | "index": "pypi", 80 | "version": "==2.5.1" 81 | }, 82 | "greenlet": { 83 | "hashes": [ 84 | "sha256:0120a879aa2b1ac5118bce959ea2492ba18783f65ea15821680a256dfad04754", 85 | "sha256:025b8de2273d2809f027d347aa2541651d2e15d593bbce0d5f502ca438c54136", 86 | "sha256:05ae7383f968bba4211b1fbfc90158f8e3da86804878442b4fb6c16ccbcaa519", 87 | "sha256:0914f02fcaa8f84f13b2df4a81645d9e82de21ed95633765dd5cc4d3af9d7403", 88 | "sha256:0971d37ae0eaf42344e8610d340aa0ad3d06cd2eee381891a10fe771879791f9", 89 | "sha256:0a954002064ee919b444b19c1185e8cce307a1f20600f47d6f4b6d336972c809", 90 | "sha256:0aa1845944e62f358d63fcc911ad3b415f585612946b8edc824825929b40e59e", 91 | "sha256:104f29dd822be678ef6b16bf0035dcd43206a8a48668a6cae4d2fe9c7a7abdeb", 92 | "sha256:11fc7692d95cc7a6a8447bb160d98671ab291e0a8ea90572d582d57361360f05", 93 | "sha256:17a69967561269b691747e7f436d75a4def47e5efcbc3c573180fc828e176d80", 94 | "sha256:2794eef1b04b5ba8948c72cc606aab62ac4b0c538b14806d9c0d88afd0576d6b", 95 | "sha256:2c6e942ca9835c0b97814d14f78da453241837419e0d26f7403058e8db3e38f8", 96 | "sha256:2ccdc818cc106cc238ff7eba0d71b9c77be868fdca31d6c3b1347a54c9b187b2", 97 | "sha256:325f272eb997916b4a3fc1fea7313a8adb760934c2140ce13a2117e1b0a8095d", 98 | "sha256:39464518a2abe9c505a727af7c0b4efff2cf242aa168be5f0daa47649f4d7ca8", 99 | "sha256:3a24f3213579dc8459e485e333330a921f579543a5214dbc935bc0763474ece3", 100 | "sha256:3aeac044c324c1a4027dca0cde550bd83a0c0fbff7ef2c98df9e718a5086c194", 101 | "sha256:3c22998bfef3fcc1b15694818fc9b1b87c6cc8398198b96b6d355a7bcb8c934e", 102 | "sha256:467b73ce5dcd89e381292fb4314aede9b12906c18fab903f995b86034d96d5c8", 103 | "sha256:4a8b58232f5b72973350c2b917ea3df0bebd07c3c82a0a0e34775fc2c1f857e9", 104 | "sha256:4f74aa0092602da2069df0bc6553919a15169d77bcdab52a21f8c5242898f519", 105 | "sha256:5662492df0588a51d5690f6578f3bbbd803e7f8d99a99f3bf6128a401be9c269", 106 | "sha256:5c2d21c2b768d8c86ad935e404cc78c30d53dea009609c3ef3a9d49970c864b5", 107 | "sha256:5edf75e7fcfa9725064ae0d8407c849456553a181ebefedb7606bac19aa1478b", 108 | "sha256:60839ab4ea7de6139a3be35b77e22e0398c270020050458b3d25db4c7c394df5", 109 | "sha256:62723e7eb85fa52e536e516ee2ac91433c7bb60d51099293671815ff49ed1c21", 110 | "sha256:64e10f303ea354500c927da5b59c3802196a07468332d292aef9ddaca08d03dd", 111 | "sha256:66aa4e9a726b70bcbfcc446b7ba89c8cec40f405e51422c39f42dfa206a96a05", 112 | "sha256:695d0d8b5ae42c800f1763c9fce9d7b94ae3b878919379150ee5ba458a460d57", 113 | "sha256:70048d7b2c07c5eadf8393e6398595591df5f59a2f26abc2f81abca09610492f", 114 | "sha256:7afa706510ab079fd6d039cc6e369d4535a48e202d042c32e2097f030a16450f", 115 | "sha256:7cf37343e43404699d58808e51f347f57efd3010cc7cee134cdb9141bd1ad9ea", 116 | "sha256:8149a6865b14c33be7ae760bcdb73548bb01e8e47ae15e013bf7ef9290ca309a", 117 | "sha256:814f26b864ed2230d3a7efe0336f5766ad012f94aad6ba43a7c54ca88dd77cba", 118 | "sha256:82a38d7d2077128a017094aff334e67e26194f46bd709f9dcdacbf3835d47ef5", 119 | "sha256:83a7a6560df073ec9de2b7cb685b199dfd12519bc0020c62db9d1bb522f989fa", 120 | "sha256:8415239c68b2ec9de10a5adf1130ee9cb0ebd3e19573c55ba160ff0ca809e012", 121 | "sha256:88720794390002b0c8fa29e9602b395093a9a766b229a847e8d88349e418b28a", 122 | "sha256:890f633dc8cb307761ec566bc0b4e350a93ddd77dc172839be122be12bae3e10", 123 | "sha256:8926a78192b8b73c936f3e87929931455a6a6c6c385448a07b9f7d1072c19ff3", 124 | "sha256:8c0581077cf2734569f3e500fab09c0ff6a2ab99b1afcacbad09b3c2843ae743", 125 | "sha256:8fda1139d87ce5f7bd80e80e54f9f2c6fe2f47983f1a6f128c47bf310197deb6", 126 | "sha256:91a84faf718e6f8b888ca63d0b2d6d185c8e2a198d2a7322d75c303e7097c8b7", 127 | "sha256:924df1e7e5db27d19b1359dc7d052a917529c95ba5b8b62f4af611176da7c8ad", 128 | "sha256:949c9061b8c6d3e6e439466a9be1e787208dec6246f4ec5fffe9677b4c19fcc3", 129 | "sha256:9649891ab4153f217f319914455ccf0b86986b55fc0573ce803eb998ad7d6854", 130 | "sha256:96656c5f7c95fc02c36d4f6ef32f4e94bb0b6b36e6a002c21c39785a4eec5f5d", 131 | "sha256:a812df7282a8fc717eafd487fccc5ba40ea83bb5b13eb3c90c446d88dbdfd2be", 132 | "sha256:a8d24eb5cb67996fb84633fdc96dbc04f2d8b12bfcb20ab3222d6be271616b67", 133 | "sha256:bef49c07fcb411c942da6ee7d7ea37430f830c482bf6e4b72d92fd506dd3a427", 134 | "sha256:bffba15cff4802ff493d6edcf20d7f94ab1c2aee7cfc1e1c7627c05f1102eee8", 135 | "sha256:c0643250dd0756f4960633f5359884f609a234d4066686754e834073d84e9b51", 136 | "sha256:c6f90234e4438062d6d09f7d667f79edcc7c5e354ba3a145ff98176f974b8132", 137 | "sha256:c8c9301e3274276d3d20ab6335aa7c5d9e5da2009cccb01127bddb5c951f8870", 138 | "sha256:c8ece5d1a99a2adcb38f69af2f07d96fb615415d32820108cd340361f590d128", 139 | "sha256:cb863057bed786f6622982fb8b2c122c68e6e9eddccaa9fa98fd937e45ee6c4f", 140 | "sha256:ccbe7129a282ec5797df0451ca1802f11578be018a32979131065565da89b392", 141 | "sha256:d25cdedd72aa2271b984af54294e9527306966ec18963fd032cc851a725ddc1b", 142 | "sha256:d75afcbb214d429dacdf75e03a1d6d6c5bd1fa9c35e360df8ea5b6270fb2211c", 143 | "sha256:d7815e1519a8361c5ea2a7a5864945906f8e386fa1bc26797b4d443ab11a4589", 144 | "sha256:eb6ac495dccb1520667cfea50d89e26f9ffb49fa28496dea2b95720d8b45eb54", 145 | "sha256:ec615d2912b9ad807afd3be80bf32711c0ff9c2b00aa004a45fd5d5dde7853d9", 146 | "sha256:f5e09dc5c6e1796969fd4b775ea1417d70e49a5df29aaa8e5d10675d9e11872c", 147 | "sha256:f6661b58412879a2aa099abb26d3c93e91dedaba55a6394d1fb1512a77e85de9", 148 | "sha256:f7d20c3267385236b4ce54575cc8e9f43e7673fc761b069c820097092e318e3b", 149 | "sha256:fe7c51f8a2ab616cb34bc33d810c887e89117771028e1e3d3b77ca25ddeace04" 150 | ], 151 | "markers": "python_version >= '3' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", 152 | "version": "==1.1.3.post0" 153 | }, 154 | "gunicorn": { 155 | "hashes": [ 156 | "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", 157 | "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" 158 | ], 159 | "index": "pypi", 160 | "version": "==20.1.0" 161 | }, 162 | "honcho": { 163 | "hashes": [ 164 | "sha256:a4d6e3a88a7b51b66351ecfc6e9d79d8f4b87351db9ad7e923f5632cc498122f", 165 | "sha256:c5eca0bded4bef6697a23aec0422fd4f6508ea3581979a3485fc4b89357eb2a9" 166 | ], 167 | "index": "pypi", 168 | "version": "==1.1.0" 169 | }, 170 | "importlib-metadata": { 171 | "hashes": [ 172 | "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab", 173 | "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43" 174 | ], 175 | "index": "pypi", 176 | "version": "==5.0.0" 177 | }, 178 | "importlib-resources": { 179 | "hashes": [ 180 | "sha256:c01b1b94210d9849f286b86bb51bcea7cd56dde0600d8db721d7b81330711668", 181 | "sha256:ee17ec648f85480d523596ce49eae8ead87d5631ae1551f913c0100b5edd3437" 182 | ], 183 | "markers": "python_version < '3.9'", 184 | "version": "==5.10.0" 185 | }, 186 | "itsdangerous": { 187 | "hashes": [ 188 | "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", 189 | "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" 190 | ], 191 | "markers": "python_version >= '3.7'", 192 | "version": "==2.1.2" 193 | }, 194 | "jinja2": { 195 | "hashes": [ 196 | "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", 197 | "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" 198 | ], 199 | "markers": "python_version >= '3.7'", 200 | "version": "==3.1.2" 201 | }, 202 | "mako": { 203 | "hashes": [ 204 | "sha256:7fde96466fcfeedb0eed94f187f20b23d85e4cb41444be0e542e2c8c65c396cd", 205 | "sha256:c413a086e38cd885088d5e165305ee8eed04e8b3f8f62df343480da0a385735f" 206 | ], 207 | "markers": "python_version >= '3.7'", 208 | "version": "==1.2.3" 209 | }, 210 | "markupsafe": { 211 | "hashes": [ 212 | "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", 213 | "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", 214 | "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", 215 | "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", 216 | "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", 217 | "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", 218 | "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", 219 | "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", 220 | "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", 221 | "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", 222 | "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", 223 | "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", 224 | "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", 225 | "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", 226 | "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", 227 | "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", 228 | "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", 229 | "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", 230 | "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", 231 | "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", 232 | "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", 233 | "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", 234 | "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", 235 | "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", 236 | "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", 237 | "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", 238 | "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", 239 | "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", 240 | "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", 241 | "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", 242 | "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", 243 | "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", 244 | "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", 245 | "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", 246 | "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", 247 | "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", 248 | "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", 249 | "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", 250 | "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", 251 | "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" 252 | ], 253 | "markers": "python_version >= '3.7'", 254 | "version": "==2.1.1" 255 | }, 256 | "psycopg2-binary": { 257 | "hashes": [ 258 | "sha256:02cde837df012fa5d579b9cf4bc8e1feb460f38d61f7a4ab4a919d55a9f6eeef", 259 | "sha256:044b6ab68613de7ea1e63856627deea091bfea09dea5ab4f050b13250fd18cab", 260 | "sha256:0a9465f0aa36480c8e7614991cbe8ca8aa16b0517c5398a49648ce345e446c19", 261 | "sha256:0d8e0c9eec79fe1ae66691e06e3cc714da6fbd77981209bf32fa823c03dbaff8", 262 | "sha256:0eae72190be519bf2629062eab7ac8d4ceec5bd132953cefa1596584d86964fe", 263 | "sha256:15e0ac0ed8a85f6049e836e95ddee627766561c85be8d23f4b3edb6ddbaa7310", 264 | "sha256:161dc52a617f0bb610a87d391cb2e77fe65b89ebfbd752f4f3217dde701ea196", 265 | "sha256:181ac372a5a5308b4076933601a9b5f0cd139b389b0aa5e164786a2abbcdb978", 266 | "sha256:1c22c59ab7d9dc110d409445f111f58556bf699b0548f3fc5176684a29c629c4", 267 | "sha256:226f11be577b70a57f4910c0ee28591d4d9fcb3d455e966267179156ae2e0c41", 268 | "sha256:24d627ed69e754c48dd142a914124858c600b4108c92546eb0ba822e63c0c6e2", 269 | "sha256:2535f44b00f26f6af0e949c825e6aecb9adcb56c965c17af5b97137fb69f00c0", 270 | "sha256:25e0517ad7ee3c5c3c69dbe3c1d95504c811e42f452b39a3505d0763b1f6caa0", 271 | "sha256:2903bf90b1e6bfc9bbfc94a1db0b50ffa9830a0ca4c042fbc38d93890c02ce08", 272 | "sha256:2f1ded23d17af0d738e7e78087f0b88a53228887845b1989b03af4dfd3fef703", 273 | "sha256:30200b07779446760813eef06098ec6d084131e4365b4e023eb43100de758b11", 274 | "sha256:33ac8b4754e6b6b21f3ee180da169d8526d91aee9408ec1fc573c16ab32b0207", 275 | "sha256:34fd249275faa782c3a2016e86ac2330636ac58d731a1580e7d686e3976b9536", 276 | "sha256:44f5dc9b4384bafca8429759ce76c8960ffc2b583fcad9e5dfb3e5f4894269e4", 277 | "sha256:451550e0bb5889bbabbf92575a6d6eafced941cc28c86be6ae4667f81bf32d67", 278 | "sha256:52383e932e6de5595963f9178cf2af7b9e1f3daacf5135b9c0e21aabbc5bf7c4", 279 | "sha256:55137faec669c4277c5687c6ce7c1fbc4dece0e2f14256ee808f4a652f0a2170", 280 | "sha256:576b9dfbcd154a0e8b5d9dae6316d037450e64a3b31df87dec71d88e2a2d5e5f", 281 | "sha256:59a3010d566a48b919490a982f6807f68842686941dc12d568e129d9cd7703d6", 282 | "sha256:61c6a258469c66412ae8358a0501df6ccb3bb48aa9c43b56624571ff9767f91d", 283 | "sha256:63edc507f8cbfbb5903adb75bad8a99f9798981c854df9119dbebab2ec3ee0e1", 284 | "sha256:65d5f4e70a2d3fbaa1349236968792611088f3f2dccead36c1626e1d183cc327", 285 | "sha256:6a1618260a112a9c93504511f0b6254b4402a8c41b7130dc6d4c9e39aff3aa0c", 286 | "sha256:704f1fcdc5b606b70563ea696c69bda90caee3a2f45ffc9cee60a901b394a79f", 287 | "sha256:7751b11cd7f6b952b4b5ec5b93b5be9ce20faba786c18c25c354f5d8717a173c", 288 | "sha256:7ad9d032dc1a31a86ca7b059f43554a049a2bfda8fe32d1492ad25f6686aff03", 289 | "sha256:7b01d07006a0ac2216921b69a220b9f0974345d0b1b36efaeabdc7550b1cc4f8", 290 | "sha256:7b47643c45e7619788c081d42e1d9d98c7c8a4933010a9967d097cc3c4c29f41", 291 | "sha256:80ed219ce6cb21a5b53ead0edf5b56b6d23de4cb95389ac606f47670474f4816", 292 | "sha256:82df4a8600999c4c0cb7d6614df1bbdb3c74732f63e79f78487893ffbed3d083", 293 | "sha256:8660112e9127a019969a23c878e1b4a419e8a6427f9a9050c19830f152628c8a", 294 | "sha256:89a86c2b35460700d04b4d6461153ab39ee85af5a5385acac9563a8310e6320a", 295 | "sha256:8d7bc25729bb6d96b44f49ad78fde0e27a1a867cb205322b7e5f5b49e04d6f1f", 296 | "sha256:97e4f3d9b17d12e7c00cb1c29c0040044135cd5146838da4274615dbe0baae78", 297 | "sha256:a431deb6ffdfa551f7400b3a94fa4b964837e67f49e3c37aa26d90dc75970816", 298 | "sha256:a6a2d3d75d8698dee492f4af7ad07606d0734e581edf9e2ce2f74b6fce90f42e", 299 | "sha256:ae5b41dbf7731b838021923edfbe3b5ccdec84d92d5795f5229c0d08d32509d9", 300 | "sha256:aff258af03dda9a990960a53759d10c3a9b936837c71fe2f3b581acd356b9121", 301 | "sha256:b216a15e13f6e763db40ac3beb74b588650bc030d10a78fde182b88d273b82b5", 302 | "sha256:b23b25b1243576b952689966205ef7d4285688068b966a1ca0e620bcb390d483", 303 | "sha256:b896637091cde69d170a89253dde9aee814b25ca204b7e213fd0a6462e666638", 304 | "sha256:d5f27b1d1b56470385faa2b2636fcb823e7ac5b5b734e0aa76b14637c66eb3b7", 305 | "sha256:d6ba33f39436191ece7ea2b3d0b4dff00af71acd5c6e6f1d6b7563aa7286e9f2", 306 | "sha256:d6c5e1df6f427d7a82606cf8f07cf3ba9fb3f366804b01e65f1f00f8df6b54f1", 307 | "sha256:e02f77b620ad6b36564fe41980865436912e21a3b1138cdde175cf24afde1bc5", 308 | "sha256:e72491d72870c3cb2f0d6f4174485533caec0e9ed7e717e2859b7cc7ff2ae1c4", 309 | "sha256:ea8d5cd689fa7225d81ae0a049ba03e0165f4ed9ca083b19a405be9ad0b36845", 310 | "sha256:eb5341fc7c53fdd95ac2415be77b1de854ab266488cff71174ebb007baf0e675", 311 | "sha256:edf0a66ce9517365c7dcfed597894d8dd1f27b59e550b77a089054101435213b", 312 | "sha256:f225784812b2b57d340f2eb0d2cebef989dcc82c288f5553e28ee9767c7c8344", 313 | "sha256:f5fbb3b325c65010e04af206a9243e2df8606736c510c7f268aca6a93e5294a9", 314 | "sha256:f78cafa25731e0b5aa16fe20bea1abf643d4e853f6bfb8a64421b06b878e2b88", 315 | "sha256:fb639a0e65dce4a9cccbcbdd8ddd0c8c6ab10bca317b827a5c52ac3c3a4ad60a", 316 | "sha256:ffb2f288f577a748cc23c65a818290755a4c2da1f87a40d7055b61a096d31e20" 317 | ], 318 | "index": "pypi", 319 | "version": "==2.9.4" 320 | }, 321 | "python-dotenv": { 322 | "hashes": [ 323 | "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5", 324 | "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045" 325 | ], 326 | "index": "pypi", 327 | "version": "==0.21.0" 328 | }, 329 | "pytz": { 330 | "hashes": [ 331 | "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22", 332 | "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914" 333 | ], 334 | "version": "==2022.5" 335 | }, 336 | "setuptools": { 337 | "hashes": [ 338 | "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17", 339 | "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356" 340 | ], 341 | "markers": "python_version >= '3.7'", 342 | "version": "==65.5.0" 343 | }, 344 | "six": { 345 | "hashes": [ 346 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 347 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 348 | ], 349 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 350 | "version": "==1.16.0" 351 | }, 352 | "sqlalchemy": { 353 | "hashes": [ 354 | "sha256:04f2598c70ea4a29b12d429a80fad3a5202d56dce19dd4916cc46a965a5ca2e9", 355 | "sha256:0501f74dd2745ec38f44c3a3900fb38b9db1ce21586b691482a19134062bf049", 356 | "sha256:0ee377eb5c878f7cefd633ab23c09e99d97c449dd999df639600f49b74725b80", 357 | "sha256:11b2ec26c5d2eefbc3e6dca4ec3d3d95028be62320b96d687b6e740424f83b7d", 358 | "sha256:15d878929c30e41fb3d757a5853b680a561974a0168cd33a750be4ab93181628", 359 | "sha256:177e41914c476ed1e1b77fd05966ea88c094053e17a85303c4ce007f88eff363", 360 | "sha256:1811a0b19a08af7750c0b69e38dec3d46e47c4ec1d74b6184d69f12e1c99a5e0", 361 | "sha256:1d0c23ecf7b3bc81e29459c34a3f4c68ca538de01254e24718a7926810dc39a6", 362 | "sha256:22459fc1718785d8a86171bbe7f01b5c9d7297301ac150f508d06e62a2b4e8d2", 363 | "sha256:28e881266a172a4d3c5929182fde6bb6fba22ac93f137d5380cc78a11a9dd124", 364 | "sha256:2e56dfed0cc3e57b2f5c35719d64f4682ef26836b81067ee6cfad062290fd9e2", 365 | "sha256:2fd49af453e590884d9cdad3586415922a8e9bb669d874ee1dc55d2bc425aacd", 366 | "sha256:3ab7c158f98de6cb4f1faab2d12973b330c2878d0c6b689a8ca424c02d66e1b3", 367 | "sha256:4948b6c5f4e56693bbeff52f574279e4ff972ea3353f45967a14c30fb7ae2beb", 368 | "sha256:4e1c5f8182b4f89628d782a183d44db51b5af84abd6ce17ebb9804355c88a7b5", 369 | "sha256:5ce6929417d5dce5ad1d3f147db81735a4a0573b8fb36e3f95500a06eaddd93e", 370 | "sha256:5ede1495174e69e273fad68ad45b6d25c135c1ce67723e40f6cf536cb515e20b", 371 | "sha256:5f966b64c852592469a7eb759615bbd351571340b8b344f1d3fa2478b5a4c934", 372 | "sha256:6045b3089195bc008aee5c273ec3ba9a93f6a55bc1b288841bd4cfac729b6516", 373 | "sha256:6c9d004eb78c71dd4d3ce625b80c96a827d2e67af9c0d32b1c1e75992a7916cc", 374 | "sha256:6e39e97102f8e26c6c8550cb368c724028c575ec8bc71afbbf8faaffe2b2092a", 375 | "sha256:723e3b9374c1ce1b53564c863d1a6b2f1dc4e97b1c178d9b643b191d8b1be738", 376 | "sha256:876eb185911c8b95342b50a8c4435e1c625944b698a5b4a978ad2ffe74502908", 377 | "sha256:9256563506e040daddccaa948d055e006e971771768df3bb01feeb4386c242b0", 378 | "sha256:934472bb7d8666727746a75670a1f8d91a9cae8c464bba79da30a0f6faccd9e1", 379 | "sha256:97ff50cd85bb907c2a14afb50157d0d5486a4b4639976b4a3346f34b6d1b5272", 380 | "sha256:9b01d9cd2f9096f688c71a3d0f33f3cd0af8549014e66a7a7dee6fc214a7277d", 381 | "sha256:9e3a65ce9ed250b2f096f7b559fe3ee92e6605fab3099b661f0397a9ac7c8d95", 382 | "sha256:a7dd5b7b34a8ba8d181402d824b87c5cee8963cb2e23aa03dbfe8b1f1e417cde", 383 | "sha256:a85723c00a636eed863adb11f1e8aaa36ad1c10089537823b4540948a8429798", 384 | "sha256:b42c59ffd2d625b28cdb2ae4cde8488543d428cba17ff672a543062f7caee525", 385 | "sha256:bd448b262544b47a2766c34c0364de830f7fb0772d9959c1c42ad61d91ab6565", 386 | "sha256:ca9389a00f639383c93ed00333ed763812f80b5ae9e772ea32f627043f8c9c88", 387 | "sha256:df76e9c60879fdc785a34a82bf1e8691716ffac32e7790d31a98d7dec6e81545", 388 | "sha256:e12c6949bae10f1012ab5c0ea52ab8db99adcb8c7b717938252137cdf694c775", 389 | "sha256:e4ef8cb3c5b326f839bfeb6af5f406ba02ad69a78c7aac0fbeeba994ad9bb48a", 390 | "sha256:e7e740453f0149437c101ea4fdc7eea2689938c5760d7dcc436c863a12f1f565", 391 | "sha256:effc89e606165ca55f04f3f24b86d3e1c605e534bf1a96e4e077ce1b027d0b71", 392 | "sha256:f0f574465b78f29f533976c06b913e54ab4980b9931b69aa9d306afff13a9471", 393 | "sha256:fa5b7eb2051e857bf83bade0641628efe5a88de189390725d3e6033a1fff4257", 394 | "sha256:fdb94a3d1ba77ff2ef11912192c066f01e68416f554c194d769391638c8ad09a" 395 | ], 396 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 397 | "version": "==1.4.42" 398 | }, 399 | "sqlalchemy-serializer": { 400 | "hashes": [ 401 | "sha256:c4cf3e3eebbffa5b46a77ddb886230e5d8c17fd0b9ddbd57eed1a837eb1463cc" 402 | ], 403 | "index": "pypi", 404 | "version": "==1.4.1" 405 | }, 406 | "werkzeug": { 407 | "hashes": [ 408 | "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", 409 | "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" 410 | ], 411 | "markers": "python_version >= '3.7'", 412 | "version": "==2.2.2" 413 | }, 414 | "zipp": { 415 | "hashes": [ 416 | "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb", 417 | "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980" 418 | ], 419 | "markers": "python_version >= '3.7'", 420 | "version": "==3.9.0" 421 | } 422 | }, 423 | "develop": {} 424 | } 425 | --------------------------------------------------------------------------------