Skip to content

Commit ea5a84b

Browse files
committed
First commit!
:wq :wq :q quit quit() :q :q :q! �
0 parents  commit ea5a84b

22 files changed

+741
-0
lines changed

.flaskenv

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FLASK_APP = app
2+
FLASK_DEBUG = 1
3+

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.venv
2+
.idea
3+
__pycache__
4+
instance
5+
worked_earlier.txt
6+
*.pyc
7+
.DS_Store

app.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import os
2+
from flask import Flask, jsonify
3+
from flask_smorest import Api
4+
from flask_jwt_extended import JWTManager
5+
from flask_migrate import Migrate
6+
from db import db
7+
from blocklist import BLOCKLIST
8+
import models
9+
from resources.item import blp as ItemBlueprint
10+
from resources.store import blp as StoreBlueprint
11+
from resources.tag import blp as TagBlueprint
12+
from resources.user import blp as UserBlueprint
13+
14+
def create_app(db_url=None):
15+
app = Flask(__name__)
16+
17+
#app.config['PROPAGATE_EXCEPTIONS'] = True
18+
app.config['API_TITLE'] = 'Stores REST API' #api needs this
19+
app.config['API_VERSION'] = 'v1' #api needs this
20+
app.config['OPENAPI_VERSION'] = '3.0.3' #api needs this
21+
app.config['OPENAPI_URL_PREFIX'] = '/' #swagger needs this
22+
app.config['OPENAPI_SWAGGER_UI_PATH'] = '/swagger-ui' #swagger needs this
23+
app.config['OPENAPI_SWAGGER_UI_URL'] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/" #swagger needs this
24+
app.config['SQLALCHEMY_DATABASE_URI'] = db_url or os.getenv('DATABASE_URL', 'sqlite:///data.db')
25+
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
26+
db.init_app(app)
27+
migrate = Migrate(app, db)
28+
api = Api(app)
29+
30+
app.config['JWT_SECRET_KEY'] = 'jose'
31+
jwt = JWTManager(app)
32+
33+
@jwt.token_in_blocklist_loader
34+
def check_if_token_in_blocklist(jwt_header, jwt_payload):
35+
return jwt_payload['jti'] in BLOCKLIST
36+
37+
@jwt.revoked_token_loader
38+
def revoked_token_callback(jwt_header, jwt_payload):
39+
return (jsonify({'descrition':'The token has been revoked','error':'token_revoked'}), 401,)
40+
41+
@jwt.needs_fresh_token_loader
42+
def token_not_fresh_callback(jwt_header, jwt_payload):
43+
return (jsonify({'description':'The token is not fresh', 'error':'fresh_token_required.'}), 401,)
44+
45+
@jwt.additional_claims_loader
46+
def add_claims_to_jwt(identity):
47+
if identity == 1:
48+
return {'is admin':True}
49+
return {'is admin':False}
50+
51+
@jwt.expired_token_loader
52+
def expired_token_callback(jwt_header, jwt_payload):
53+
return(jsonify({'message':'The token has expired', 'error':'token_expired'}), 401,)
54+
55+
@jwt.invalid_token_loader
56+
def invalid_token_callbak(error):
57+
return (jsonify({'message':'Signature verification failed','error':'invalid token'}), 401,)
58+
59+
@jwt.unauthorized_loader
60+
def missing_token_callback(error):
61+
return (jsonify({'description':'Request does not contain an access token', 'error':'authorizatiion_required'}))
62+
63+
#with app.app_context():
64+
# db.create_all()
65+
api.register_blueprint(StoreBlueprint)
66+
api.register_blueprint(ItemBlueprint)
67+
api.register_blueprint(TagBlueprint)
68+
api.register_blueprint(UserBlueprint)
69+
return app
70+
71+
72+
73+
74+

blocklist.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
BLOCKLIST = set() #for storing JWT is the best in the DATABASE or the best best is REDIS

db.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from flask_sqlalchemy import SQLAlchemy
2+
3+
db = SQLAlchemy()

migrations/README

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Single-database configuration for Flask.

migrations/alembic.ini

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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

migrations/env.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import logging
2+
from logging.config import fileConfig
3+
4+
from flask import current_app
5+
6+
from alembic import context
7+
8+
# this is the Alembic Config object, which provides
9+
# access to the values within the .ini file in use.
10+
config = context.config
11+
12+
# Interpret the config file for Python logging.
13+
# This line sets up loggers basically.
14+
fileConfig(config.config_file_name)
15+
logger = logging.getLogger('alembic.env')
16+
17+
18+
def get_engine():
19+
try:
20+
# this works with Flask-SQLAlchemy<3 and Alchemical
21+
return current_app.extensions['migrate'].db.get_engine()
22+
except TypeError:
23+
# this works with Flask-SQLAlchemy>=3
24+
return current_app.extensions['migrate'].db.engine
25+
26+
27+
def get_engine_url():
28+
try:
29+
return get_engine().url.render_as_string(hide_password=False).replace(
30+
'%', '%%')
31+
except AttributeError:
32+
return str(get_engine().url).replace('%', '%%')
33+
34+
35+
# add your model's MetaData object here
36+
# for 'autogenerate' support
37+
# from myapp import mymodel
38+
# target_metadata = mymodel.Base.metadata
39+
config.set_main_option('sqlalchemy.url', get_engine_url())
40+
target_db = current_app.extensions['migrate'].db
41+
42+
# other values from the config, defined by the needs of env.py,
43+
# can be acquired:
44+
# my_important_option = config.get_main_option("my_important_option")
45+
# ... etc.
46+
47+
48+
def get_metadata():
49+
if hasattr(target_db, 'metadatas'):
50+
return target_db.metadatas[None]
51+
return target_db.metadata
52+
53+
54+
def run_migrations_offline():
55+
"""Run migrations in 'offline' mode.
56+
57+
This configures the context with just a URL
58+
and not an Engine, though an Engine is acceptable
59+
here as well. By skipping the Engine creation
60+
we don't even need a DBAPI to be available.
61+
62+
Calls to context.execute() here emit the given string to the
63+
script output.
64+
65+
"""
66+
url = config.get_main_option("sqlalchemy.url")
67+
context.configure(
68+
url=url, target_metadata=get_metadata(), literal_binds=True
69+
)
70+
71+
with context.begin_transaction():
72+
context.run_migrations()
73+
74+
75+
def run_migrations_online():
76+
"""Run migrations in 'online' mode.
77+
78+
In this scenario we need to create an Engine
79+
and associate a connection with the context.
80+
81+
"""
82+
83+
# this callback is used to prevent an auto-migration from being generated
84+
# when there are no changes to the schema
85+
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
86+
def process_revision_directives(context, revision, directives):
87+
if getattr(config.cmd_opts, 'autogenerate', False):
88+
script = directives[0]
89+
if script.upgrade_ops.is_empty():
90+
directives[:] = []
91+
logger.info('No changes in schema detected.')
92+
93+
connectable = get_engine()
94+
95+
with connectable.connect() as connection:
96+
context.configure(
97+
connection=connection,
98+
target_metadata=get_metadata(),
99+
process_revision_directives=process_revision_directives,
100+
**current_app.extensions['migrate'].configure_args
101+
)
102+
103+
with context.begin_transaction():
104+
context.run_migrations()
105+
106+
107+
if context.is_offline_mode():
108+
run_migrations_offline()
109+
else:
110+
run_migrations_online()

migrations/script.py.mako

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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"}

migrations/versions/1bbe1d722e6d_.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""empty message
2+
3+
Revision ID: 1bbe1d722e6d
4+
Revises:
5+
Create Date: 2023-03-04 23:40:50.487046
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '1bbe1d722e6d'
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('stores',
22+
sa.Column('id', sa.Integer(), nullable=False),
23+
sa.Column('name', sa.String(length=80), nullable=False),
24+
sa.PrimaryKeyConstraint('id'),
25+
sa.UniqueConstraint('name')
26+
)
27+
op.create_table('users',
28+
sa.Column('id', sa.Integer(), nullable=False),
29+
sa.Column('username', sa.String(length=80), nullable=False),
30+
sa.Column('password', sa.String(length=80), nullable=False),
31+
sa.PrimaryKeyConstraint('id'),
32+
sa.UniqueConstraint('username')
33+
)
34+
op.create_table('items',
35+
sa.Column('id', sa.Integer(), nullable=False),
36+
sa.Column('name', sa.String(length=80), nullable=False),
37+
sa.Column('price', sa.Float(precision=2), nullable=False),
38+
sa.Column('store_id', sa.Integer(), nullable=False),
39+
sa.ForeignKeyConstraint(['store_id'], ['stores.id'], ),
40+
sa.PrimaryKeyConstraint('id'),
41+
sa.UniqueConstraint('name')
42+
)
43+
op.create_table('tags',
44+
sa.Column('id', sa.Integer(), nullable=False),
45+
sa.Column('name', sa.String(length=80), nullable=False),
46+
sa.Column('store_id', sa.Integer(), nullable=False),
47+
sa.ForeignKeyConstraint(['store_id'], ['stores.id'], ),
48+
sa.PrimaryKeyConstraint('id'),
49+
sa.UniqueConstraint('name')
50+
)
51+
op.create_table('items_tags',
52+
sa.Column('id', sa.Integer(), nullable=False),
53+
sa.Column('item_id', sa.Integer(), nullable=True),
54+
sa.Column('tag_id', sa.Integer(), nullable=True),
55+
sa.ForeignKeyConstraint(['item_id'], ['items.id'], ),
56+
sa.ForeignKeyConstraint(['tag_id'], ['tags.id'], ),
57+
sa.PrimaryKeyConstraint('id')
58+
)
59+
# ### end Alembic commands ###
60+
61+
62+
def downgrade():
63+
# ### commands auto generated by Alembic - please adjust! ###
64+
op.drop_table('items_tags')
65+
op.drop_table('tags')
66+
op.drop_table('items')
67+
op.drop_table('users')
68+
op.drop_table('stores')
69+
# ### end Alembic commands ###

migrations/versions/8ab68295c0d2_.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""empty message
2+
3+
Revision ID: 8ab68295c0d2
4+
Revises: 1bbe1d722e6d
5+
Create Date: 2023-03-04 23:48:21.099340
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '8ab68295c0d2'
14+
down_revision = '1bbe1d722e6d'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
with op.batch_alter_table('items', schema=None) as batch_op:
22+
batch_op.add_column(sa.Column('description', sa.String(), nullable=True))
23+
24+
# ### end Alembic commands ###
25+
26+
27+
def downgrade():
28+
# ### commands auto generated by Alembic - please adjust! ###
29+
with op.batch_alter_table('items', schema=None) as batch_op:
30+
batch_op.drop_column('description')
31+
32+
# ### end Alembic commands ###

models/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from models.store import StoreModel
2+
from models.item import ItemModel
3+
from models.tag import TagModel
4+
from models.item_tags import ItemTags
5+
from models.user import UserModel

0 commit comments

Comments
 (0)