├── .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 |
7 | {birds.map((bird) => {
8 | return ;
9 | })}
10 |
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 |
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 |

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 |
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 |
--------------------------------------------------------------------------------