Skip to content

Commit de9b833

Browse files
committed
python fastapi access and refresh token
0 parents  commit de9b833

20 files changed

+661
-0
lines changed

Diff for: .env

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
DATABASE_PORT=6500
2+
POSTGRES_PASSWORD=password123
3+
POSTGRES_USER=postgres
4+
POSTGRES_DB=fastapi
5+
POSTGRES_HOST=postgres
6+
POSTGRES_HOSTNAME=127.0.0.1
7+
8+
ACCESS_TOKEN_EXPIRES_IN=15
9+
REFRESH_TOKEN_EXPIRES_IN=60
10+
JWT_ALGORITHM=RS256
11+
12+
CLIENT_ORIGIN=http://localhost:3000
13+
14+
15+
JWT_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT2dJQkFBSkJBSSs3QnZUS0FWdHVQYzEzbEFkVk94TlVmcWxzMm1SVmlQWlJyVFpjd3l4RVhVRGpNaFZuCi9KVHRsd3h2a281T0pBQ1k3dVE0T09wODdiM3NOU3ZNd2xNQ0F3RUFBUUpBYm5LaENOQ0dOSFZGaHJPQ0RCU0IKdmZ2ckRWUzVpZXAwd2h2SGlBUEdjeWV6bjd0U2RweUZ0NEU0QTNXT3VQOXhqenNjTFZyb1pzRmVMUWlqT1JhUwp3UUloQU84MWl2b21iVGhjRkltTFZPbU16Vk52TGxWTW02WE5iS3B4bGh4TlpUTmhBaUVBbWRISlpGM3haWFE0Cm15QnNCeEhLQ3JqOTF6bVFxU0E4bHUvT1ZNTDNSak1DSVFEbDJxOUdtN0lMbS85b0EyaCtXdnZabGxZUlJPR3oKT21lV2lEclR5MUxaUVFJZ2ZGYUlaUWxMU0tkWjJvdXF4MHdwOWVEejBEWklLVzVWaSt6czdMZHRDdUVDSUVGYwo3d21VZ3pPblpzbnU1clBsTDJjZldLTGhFbWwrUVFzOCtkMFBGdXlnCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t
16+
JWT_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBSSs3QnZUS0FWdHVQYzEzbEFkVk94TlVmcWxzMm1SVgppUFpSclRaY3d5eEVYVURqTWhWbi9KVHRsd3h2a281T0pBQ1k3dVE0T09wODdiM3NOU3ZNd2xNQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==

Diff for: .gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__pycache__
2+
venv/
3+
# .env

Diff for: Makefile

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
dev:
2+
docker-compose up -d
3+
4+
dev-down:
5+
docker-compose down

Diff for: alembic.ini

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# path to migration scripts
5+
script_location = alembic
6+
7+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
8+
# Uncomment the line below if you want the files to be prepended with date and time
9+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
10+
# for all available tokens
11+
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
12+
13+
# sys.path path, will be prepended to sys.path if present.
14+
# defaults to the current working directory.
15+
prepend_sys_path = .
16+
17+
# timezone to use when rendering the date within the migration file
18+
# as well as the filename.
19+
# If specified, requires the python-dateutil library that can be
20+
# installed by adding `alembic[tz]` to the pip requirements
21+
# string value is passed to dateutil.tz.gettz()
22+
# leave blank for localtime
23+
# timezone =
24+
25+
# max length of characters to apply to the
26+
# "slug" field
27+
# truncate_slug_length = 40
28+
29+
# set to 'true' to run the environment during
30+
# the 'revision' command, regardless of autogenerate
31+
# revision_environment = false
32+
33+
# set to 'true' to allow .pyc and .pyo files without
34+
# a source .py file to be detected as revisions in the
35+
# versions/ directory
36+
# sourceless = false
37+
38+
# version location specification; This defaults
39+
# to alembic/versions. When using multiple version
40+
# directories, initial revisions must be specified with --version-path.
41+
# The path separator used here should be the separator specified by "version_path_separator" below.
42+
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
43+
44+
# version path separator; As mentioned above, this is the character used to split
45+
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
46+
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
47+
# Valid values for version_path_separator are:
48+
#
49+
# version_path_separator = :
50+
# version_path_separator = ;
51+
# version_path_separator = space
52+
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
53+
54+
# the output encoding used when revision files
55+
# are written from script.py.mako
56+
# output_encoding = utf-8
57+
58+
sqlalchemy.url = driver://user:pass@localhost/dbname
59+
60+
61+
[post_write_hooks]
62+
# post_write_hooks defines scripts or Python functions that are run
63+
# on newly generated revision scripts. See the documentation for further
64+
# detail and examples
65+
66+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
67+
# hooks = black
68+
# black.type = console_scripts
69+
# black.entrypoint = black
70+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
71+
72+
# Logging configuration
73+
[loggers]
74+
keys = root,sqlalchemy,alembic
75+
76+
[handlers]
77+
keys = console
78+
79+
[formatters]
80+
keys = generic
81+
82+
[logger_root]
83+
level = WARN
84+
handlers = console
85+
qualname =
86+
87+
[logger_sqlalchemy]
88+
level = WARN
89+
handlers =
90+
qualname = sqlalchemy.engine
91+
92+
[logger_alembic]
93+
level = INFO
94+
handlers =
95+
qualname = alembic
96+
97+
[handler_console]
98+
class = StreamHandler
99+
args = (sys.stderr,)
100+
level = NOTSET
101+
formatter = generic
102+
103+
[formatter_generic]
104+
format = %(levelname)-5.5s [%(name)s] %(message)s
105+
datefmt = %H:%M:%S

Diff for: alembic/README

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Generic single-database configuration.

Diff for: alembic/env.py

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from logging.config import fileConfig
2+
3+
from sqlalchemy import engine_from_config
4+
from sqlalchemy import pool
5+
6+
from alembic import context
7+
from app.config import settings
8+
from app.models import Base
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+
config.set_main_option(
14+
"sqlalchemy.url", f"postgresql+psycopg2://{settings.POSTGRES_USER}:{settings.POSTGRES_PASSWORD}@{settings.POSTGRES_HOSTNAME}:{settings.DATABASE_PORT}/{settings.POSTGRES_DB}")
15+
16+
# Interpret the config file for Python logging.
17+
# This line sets up loggers basically.
18+
if config.config_file_name is not None:
19+
fileConfig(config.config_file_name)
20+
21+
# add your model's MetaData object here
22+
# for 'autogenerate' support
23+
# from myapp import mymodel
24+
# target_metadata = mymodel.Base.metadata
25+
target_metadata = Base.metadata
26+
27+
# other values from the config, defined by the needs of env.py,
28+
# can be acquired:
29+
# my_important_option = config.get_main_option("my_important_option")
30+
# ... etc.
31+
32+
33+
def run_migrations_offline() -> None:
34+
"""Run migrations in 'offline' mode.
35+
36+
This configures the context with just a URL
37+
and not an Engine, though an Engine is acceptable
38+
here as well. By skipping the Engine creation
39+
we don't even need a DBAPI to be available.
40+
41+
Calls to context.execute() here emit the given string to the
42+
script output.
43+
44+
"""
45+
url = config.get_main_option("sqlalchemy.url")
46+
context.configure(
47+
url=url,
48+
target_metadata=target_metadata,
49+
literal_binds=True,
50+
dialect_opts={"paramstyle": "named"},
51+
)
52+
53+
with context.begin_transaction():
54+
context.run_migrations()
55+
56+
57+
def run_migrations_online() -> None:
58+
"""Run migrations in 'online' mode.
59+
60+
In this scenario we need to create an Engine
61+
and associate a connection with the context.
62+
63+
"""
64+
connectable = engine_from_config(
65+
config.get_section(config.config_ini_section),
66+
prefix="sqlalchemy.",
67+
poolclass=pool.NullPool,
68+
)
69+
70+
with connectable.connect() as connection:
71+
context.configure(
72+
connection=connection, target_metadata=target_metadata
73+
)
74+
75+
with context.begin_transaction():
76+
context.run_migrations()
77+
78+
79+
if context.is_offline_mode():
80+
run_migrations_offline()
81+
else:
82+
run_migrations_online()

Diff for: alembic/script.py.mako

+24
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() -> None:
20+
${upgrades if upgrades else "pass"}
21+
22+
23+
def downgrade() -> None:
24+
${downgrades if downgrades else "pass"}

Diff for: alembic/versions/15770e820938_created_users_table.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""created_users_table
2+
3+
Revision ID: 15770e820938
4+
Revises:
5+
Create Date: 2022-07-06 15:11:26.439123
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from sqlalchemy.dialects import postgresql
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '15770e820938'
14+
down_revision = None
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade() -> None:
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table('users',
22+
sa.Column('id', postgresql.UUID(), server_default=sa.text('uuid_generate_v4()'), nullable=False),
23+
sa.Column('name', sa.String(), nullable=False),
24+
sa.Column('email', sa.String(), nullable=False),
25+
sa.Column('password', sa.String(), nullable=False),
26+
sa.Column('photo', sa.String(), nullable=True),
27+
sa.Column('verified', sa.Boolean(), server_default='False', nullable=False),
28+
sa.Column('role', sa.String(), server_default='user', nullable=False),
29+
sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
30+
sa.Column('updated_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
31+
sa.PrimaryKeyConstraint('id'),
32+
sa.UniqueConstraint('email')
33+
)
34+
# ### end Alembic commands ###
35+
36+
37+
def downgrade() -> None:
38+
# ### commands auto generated by Alembic - please adjust! ###
39+
op.drop_table('users')
40+
# ### end Alembic commands ###

Diff for: app/__init__.py

Whitespace-only changes.

Diff for: app/config.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from pydantic import BaseSettings, EmailStr
2+
3+
4+
class Settings(BaseSettings):
5+
DATABASE_PORT: int
6+
POSTGRES_PASSWORD: str
7+
POSTGRES_USER: str
8+
POSTGRES_DB: str
9+
POSTGRES_HOST: str
10+
POSTGRES_HOSTNAME: str
11+
12+
JWT_PUBLIC_KEY: str
13+
JWT_PRIVATE_KEY: str
14+
REFRESH_TOKEN_EXPIRES_IN: int
15+
ACCESS_TOKEN_EXPIRES_IN: int
16+
JWT_ALGORITHM: str
17+
18+
CLIENT_ORIGIN: str
19+
20+
class Config:
21+
env_file = './.env'
22+
23+
24+
settings = Settings()

Diff for: app/database.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from sqlalchemy import create_engine
2+
from sqlalchemy.ext.declarative import declarative_base
3+
from sqlalchemy.orm import sessionmaker
4+
from .config import settings
5+
6+
SQLALCHEMY_DATABASE_URL = f"postgresql://{settings.POSTGRES_USER}:{settings.POSTGRES_PASSWORD}@{settings.POSTGRES_HOSTNAME}:{settings.DATABASE_PORT}/{settings.POSTGRES_DB}"
7+
8+
engine = create_engine(
9+
SQLALCHEMY_DATABASE_URL
10+
)
11+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
12+
13+
Base = declarative_base()
14+
15+
16+
def get_db():
17+
db = SessionLocal()
18+
try:
19+
yield db
20+
finally:
21+
db.close()

Diff for: app/main.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from fastapi import FastAPI
2+
from fastapi.middleware.cors import CORSMiddleware
3+
from app.config import settings
4+
from app.routers import user, auth
5+
6+
app = FastAPI()
7+
8+
origins = [
9+
settings.CLIENT_ORIGIN,
10+
]
11+
12+
app.add_middleware(
13+
CORSMiddleware,
14+
allow_origins=origins,
15+
allow_credentials=True,
16+
allow_methods=["*"],
17+
allow_headers=["*"],
18+
)
19+
20+
21+
app.include_router(auth.router, tags=['Auth'], prefix='/api/auth')
22+
app.include_router(user.router, tags=['Users'], prefix='/api/users')
23+
24+
25+
@app.get('/api/healthchecker')
26+
def root():
27+
return {'message': 'Hello World'}

Diff for: app/models.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from uuid import UUID
2+
from .database import Base
3+
from sqlalchemy import TIMESTAMP, Column, String, Boolean, text
4+
from sqlalchemy.dialects.postgresql import UUID
5+
6+
7+
class User(Base):
8+
__tablename__ = 'users'
9+
id = Column(UUID, primary_key=True, nullable=False,
10+
server_default=text("uuid_generate_v4()"))
11+
name = Column(String, nullable=False)
12+
email = Column(String, unique=True, nullable=False)
13+
password = Column(String, nullable=False)
14+
photo = Column(String, nullable=True)
15+
verified = Column(Boolean, nullable=False, server_default='False')
16+
role = Column(String, server_default='user', nullable=False)
17+
created_at = Column(TIMESTAMP(timezone=True),
18+
nullable=False, server_default=text("now()"))
19+
updated_at = Column(TIMESTAMP(timezone=True),
20+
nullable=False, server_default=text("now()"))

0 commit comments

Comments
 (0)