feat: front end for login

This commit is contained in:
2025-06-14 10:02:02 -04:00
parent df5b247cdd
commit 2729ba49f2
18 changed files with 531 additions and 68 deletions
-1
View File
@@ -1 +0,0 @@
Generic single-database configuration.
+25
View File
@@ -0,0 +1,25 @@
# Alembic
Alembic allows for our database migrations to be tracked in a version control system.
To create a new migration run:
```bash
alembic revision --autogenerate -m 'Describe change here'
```
It's best practice to review the script post revision creation: `alembic/versions`
To apply the migration:
```bash
alembic upgrade head
```
Now you can re-check using `alembic check`
If we need to rollback use:
```bash
alembic downgrade -i
```
@@ -0,0 +1,58 @@
"""1.1.0
Revision ID: b9dcd098debd
Revises: a3ac646e53a8
Create Date: 2025-06-14 09:22:14.878105
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = 'b9dcd098debd'
down_revision: Union[str, None] = 'a3ac646e53a8'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_users_email'), table_name='users')
op.drop_index(op.f('ix_users_id'), table_name='users')
op.drop_index(op.f('ix_users_username'), table_name='users')
op.drop_table('users')
op.drop_index(op.f('ix_items_id'), table_name='items')
op.drop_index(op.f('ix_items_name'), table_name='items')
op.drop_table('items')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('items',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('description', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('body', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('items_pkey'))
)
op.create_index(op.f('ix_items_name'), 'items', ['name'], unique=False)
op.create_index(op.f('ix_items_id'), 'items', ['id'], unique=False)
op.create_table('users',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('username', sa.VARCHAR(), autoincrement=False, nullable=False),
sa.Column('email', sa.VARCHAR(), autoincrement=False, nullable=False),
sa.Column('hashed_password', sa.VARCHAR(), autoincrement=False, nullable=False),
sa.Column('permissions', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=False),
sa.Column('subscriber', sa.BOOLEAN(), autoincrement=False, nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('users_pkey'))
)
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
# ### end Alembic commands ###
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+7 -3
View File
@@ -31,10 +31,10 @@ def delete_item(db: Session, item_id: int):
def authenticate_user(db: Session, username: str, password: str):
user = get_user_by_username(db, username)
user = get_user_by_username(db, username) or get_user_by_email(db, username)
if not user:
return None
if not verify_password(password, str(user.hashed_password)):
if not verify_password(password, user.hashed_password):
return None
return user
@@ -50,7 +50,11 @@ def get_user_by_email(db: Session, email: str):
def create_user(db: Session, user: schemas.UserCreate):
hashed_pw = hash_password(user.password)
db_user = models.User(
username=user.username, email=user.email, hashed_password=hashed_pw
username=user.username,
email=user.email,
hashed_password=hashed_pw,
permissions=user.permissions,
subscriber=user.subscriber,
)
db.add(db_user)
db.commit()
+10
View File
@@ -1,4 +1,5 @@
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
@@ -10,6 +11,14 @@ Base.metadata.create_all(bind=engine)
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000", "https://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Dependency
def get_db():
@@ -81,4 +90,5 @@ def register_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
status_code=status.HTTP_400_BAD_REQUEST,
detail="Account with that email already registered",
)
# Default Cases
return crud.create_user(db, user)
+3 -1
View File
@@ -24,13 +24,15 @@ class UserBase(BaseModel):
class UserCreate(UserBase):
password: str
permissions: dict = {}
subscriber: bool = False
class UserOut(UserBase):
id: int
class Config:
orm_mode = True
from_attributes = True
# Other Schemas
+3 -3
View File
@@ -1,4 +1,3 @@
import logging
import os
from typing import Any, Mapping
from passlib.context import CryptContext
@@ -6,7 +5,8 @@ from datetime import UTC, datetime, timedelta
from jose import JWTError, jwt
from app.logger_config import Logger
pwd_context = CryptContext(schemas=["bcrypt"], deprecated="auto")
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
_logger = Logger().logger
def hash_password(password: str) -> str:
@@ -37,5 +37,5 @@ def decode_access_token(token: str) -> Mapping[Any, Any] | None:
try:
return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
except JWTError:
logging.exception(msg="Failed to Decode JWT", extra={"TOKEN": token})
_logger.exception(msg="Failed to Decode JWT", extra={"TOKEN": token})
return None
-1
View File
@@ -10,7 +10,6 @@ dependencies = [
"fastapi[standard]>=0.115.12",
"passlib[bcrypt]>=1.7.4",
"psycopg2-binary>=2.9.10",
"pyrefly>=0.18.1",
"python-jose[cryptography]>=3.5.0",
"sqlalchemy>=2.0.41",
"uvicorn[standard]>=0.34.2",