backend changes
This commit is contained in:
parent
e5a5e141a9
commit
0d34959e08
|
@ -0,0 +1,31 @@
|
||||||
|
aiosqlite==0.17.0
|
||||||
|
alembic==1.6.5
|
||||||
|
asgiref==3.4.1
|
||||||
|
bcrypt==3.2.0
|
||||||
|
cffi==1.14.6
|
||||||
|
click==8.0.1
|
||||||
|
databases==0.4.3
|
||||||
|
Deprecated==1.2.12
|
||||||
|
dnspython==2.1.0
|
||||||
|
email-validator==1.1.3
|
||||||
|
fastapi==0.68.0
|
||||||
|
fastapi-users==6.1.2
|
||||||
|
h11==0.12.0
|
||||||
|
idna==3.2
|
||||||
|
makefun==1.11.3
|
||||||
|
Mako==1.1.4
|
||||||
|
MarkupSafe==2.0.1
|
||||||
|
ormar==0.10.16
|
||||||
|
passlib==1.7.4
|
||||||
|
pycparser==2.20
|
||||||
|
pydantic==1.8.2
|
||||||
|
PyJWT==2.1.0
|
||||||
|
python-dateutil==2.8.2
|
||||||
|
python-editor==1.0.4
|
||||||
|
python-multipart==0.0.5
|
||||||
|
six==1.16.0
|
||||||
|
SQLAlchemy==1.3.23
|
||||||
|
starlette==0.14.2
|
||||||
|
typing-extensions==3.7.4.3
|
||||||
|
uvicorn==0.15.0
|
||||||
|
wrapt==1.12.1
|
|
@ -1,19 +1,20 @@
|
||||||
|
from .db import database, engine, Base, DbSession
|
||||||
|
from .users import add_user_routers, User
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from users import add_user_routers, User
|
from .routes import create_api_router
|
||||||
from db import database, engine, Base, DbSession
|
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
from routes import router as api_router
|
|
||||||
import models
|
|
||||||
|
|
||||||
|
|
||||||
def get_app() -> FastAPI:
|
def get_app(database) -> FastAPI:
|
||||||
application = FastAPI(title="swimtracker", debug=True, version="0.1")
|
application = FastAPI(title="swimtracker", debug=True, version="0.1")
|
||||||
application.include_router(api_router)
|
fastapi_users = add_user_routers(application, database)
|
||||||
add_user_routers(application)
|
current_user = fastapi_users.current_user(active=True, verified=True)
|
||||||
|
current_superuser = fastapi_users.current_user(active=True, superuser=True, verified=True)
|
||||||
|
application.include_router(create_api_router(current_user))
|
||||||
return application
|
return application
|
||||||
|
|
||||||
|
|
||||||
app = get_app()
|
app = get_app(database)
|
||||||
|
|
||||||
|
|
||||||
@app.middleware("http")
|
@app.middleware("http")
|
|
@ -1,7 +1,6 @@
|
||||||
import databases
|
import databases
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from starlette import requests
|
from .config import DATABASE_URL
|
||||||
from config import DATABASE_URL
|
|
||||||
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
|
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from db import Base
|
from .db import Base
|
||||||
from sqlalchemy import Column, Integer, Index, LargeBinary, ForeignKey, and_, or_
|
from sqlalchemy import Column, Integer, Index, LargeBinary, ForeignKey, and_, or_
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
|
|
||||||
class Session(Base):
|
class Session(Base):
|
||||||
|
|
|
@ -1,92 +1,93 @@
|
||||||
import base64
|
from . import models
|
||||||
|
from . import schemas
|
||||||
|
from .db import get_db
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from typing import List
|
|
||||||
import schemas
|
|
||||||
from users import User, UserDB, UserTable, current_user
|
|
||||||
from db import get_db
|
|
||||||
import models
|
|
||||||
from sqlalchemy.orm import Session as DbSession, lazyload
|
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
|
||||||
from sqlalchemy.exc import IntegrityError
|
|
||||||
from fastapi import status
|
from fastapi import status
|
||||||
from sqlalchemy.sql import select
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
from sqlalchemy.orm import Session as DbSession
|
||||||
router = APIRouter()
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
from typing import List
|
||||||
|
from .users import User, UserTable
|
||||||
|
import base64
|
||||||
|
|
||||||
|
|
||||||
@router.post("/sessions",
|
def create_api_router(current_user):
|
||||||
response_model=schemas.Session,
|
router = APIRouter()
|
||||||
tags=["sessions"],
|
|
||||||
status_code=status.HTTP_201_CREATED)
|
|
||||||
def create_session(session: schemas.SessionBase,
|
|
||||||
db: DbSession = Depends(get_db),
|
|
||||||
user: User = Depends(current_user)):
|
|
||||||
session_props = session.dict()
|
|
||||||
session_props['user'] = user.id
|
|
||||||
session_props['data'] = base64.b64decode(session_props['data'])
|
|
||||||
db_obj = models.Session(**session_props)
|
|
||||||
db.add(db_obj)
|
|
||||||
db.commit()
|
|
||||||
return db_obj
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/sessions", response_model=List[schemas.Session], tags=["sessions"])
|
@router.post("/sessions",
|
||||||
def list_sessions(skip=0,
|
response_model=schemas.Session,
|
||||||
limit=100,
|
tags=["sessions"],
|
||||||
db: DbSession = Depends(get_db),
|
status_code=status.HTTP_201_CREATED)
|
||||||
user: User = Depends(current_user)):
|
def create_session(session: schemas.SessionBase,
|
||||||
return db.query(models.Session).filter(models.Session.user == user.id).order_by(
|
db: DbSession = Depends(get_db),
|
||||||
models.Session.start_time.desc()).offset(skip).limit(limit).all()
|
user: User = Depends(current_user)):
|
||||||
|
session_props = session.dict()
|
||||||
|
session_props['user'] = user.id
|
||||||
|
session_props['data'] = base64.b64decode(session_props['data'])
|
||||||
|
db_obj = models.Session(**session_props)
|
||||||
|
db.add(db_obj)
|
||||||
|
db.commit()
|
||||||
|
return db_obj
|
||||||
|
|
||||||
|
|
||||||
@router.post("/friends/request_friendship/{user_id}", tags=["friends"])
|
@router.get("/sessions", response_model=List[schemas.Session], tags=["sessions"])
|
||||||
def create_friend_request(other_user_id: str,
|
def list_sessions(skip=0,
|
||||||
db: DbSession = Depends(get_db),
|
limit=100,
|
||||||
user: User = Depends(current_user)):
|
db: DbSession = Depends(get_db),
|
||||||
if models.Friendship.are_friends(db, other_user_id, user.id):
|
user: User = Depends(current_user)):
|
||||||
raise HTTPException(status.HTTP_406_NOT_ACCEPTABLE, detail="already friends")
|
return db.query(models.Session).filter(models.Session.user == user.id).order_by(
|
||||||
|
models.Session.start_time.desc()).offset(skip).limit(limit).all()
|
||||||
|
|
||||||
FR = models.FriendRequest
|
|
||||||
friend_request_from_other_user = db.query(FR).filter(FR.requesting_user == other_user_id,
|
@router.post("/friends/request_friendship/{user_id}", tags=["friends"])
|
||||||
FR.receiving_user == user.id).count()
|
def create_friend_request(other_user_id: str,
|
||||||
if friend_request_from_other_user > 0:
|
db: DbSession = Depends(get_db),
|
||||||
raise HTTPException(status.HTTP_406_NOT_ACCEPTABLE,
|
user: User = Depends(current_user)):
|
||||||
detail="Friend request exist from other user, accept it")
|
if models.Friendship.are_friends(db, other_user_id, user.id):
|
||||||
else:
|
raise HTTPException(status.HTTP_406_NOT_ACCEPTABLE, detail="already friends")
|
||||||
try:
|
|
||||||
new_friend_request = FR(requesting_user=user.id, receiving_user=other_user_id)
|
FR = models.FriendRequest
|
||||||
db.add(new_friend_request)
|
friend_request_from_other_user = db.query(FR).filter(FR.requesting_user == other_user_id,
|
||||||
db.commit()
|
FR.receiving_user == user.id).count()
|
||||||
return {"msg": "ok"}
|
if friend_request_from_other_user > 0:
|
||||||
except IntegrityError:
|
|
||||||
raise HTTPException(status.HTTP_406_NOT_ACCEPTABLE,
|
raise HTTPException(status.HTTP_406_NOT_ACCEPTABLE,
|
||||||
detail="Friend request already exists")
|
detail="Friend request exist from other user, accept it")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
new_friend_request = FR(requesting_user=user.id, receiving_user=other_user_id)
|
||||||
|
db.add(new_friend_request)
|
||||||
|
db.commit()
|
||||||
|
return {"msg": "ok"}
|
||||||
|
except IntegrityError:
|
||||||
|
raise HTTPException(status.HTTP_406_NOT_ACCEPTABLE,
|
||||||
|
detail="Friend request already exists")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/friends/accept_friendship/{user_id}", tags=["friends"])
|
@router.post("/friends/accept_friendship/{user_id}", tags=["friends"])
|
||||||
def accept_friend_request(other_user_id: str,
|
def accept_friend_request(other_user_id: str,
|
||||||
db: DbSession = Depends(get_db),
|
db: DbSession = Depends(get_db),
|
||||||
user: User = Depends(current_user)):
|
user: User = Depends(current_user)):
|
||||||
FR = models.FriendRequest
|
FR = models.FriendRequest
|
||||||
try:
|
try:
|
||||||
friend_request = db.query(FR).filter(FR.requesting_user == other_user_id,
|
friend_request = db.query(FR).filter(FR.requesting_user == other_user_id,
|
||||||
FR.receiving_user == user.id).one()
|
FR.receiving_user == user.id).one()
|
||||||
except NoResultFound:
|
except NoResultFound:
|
||||||
raise HTTPException(status_code=404, detail="No matching friend request found")
|
raise HTTPException(status_code=404, detail="No matching friend request found")
|
||||||
|
|
||||||
models.Friendship.befriend(db, other_user_id, user.id)
|
models.Friendship.befriend(db, other_user_id, user.id)
|
||||||
db.delete(friend_request)
|
db.delete(friend_request)
|
||||||
db.commit()
|
db.commit()
|
||||||
return {"msg": "ok"}
|
return {"msg": "ok"}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/friends", tags=["friends"], response_model=schemas.FriendsInfo)
|
@router.get("/friends", tags=["friends"], response_model=schemas.FriendsInfo)
|
||||||
def list_friends_info(db: DbSession = Depends(get_db), user: User = Depends(current_user)):
|
def list_friends_info(db: DbSession = Depends(get_db), user: User = Depends(current_user)):
|
||||||
user_obj = db.query(UserTable).filter(UserTable.id == user.id).one()
|
user_obj = db.query(UserTable).filter(UserTable.id == user.id).one()
|
||||||
return schemas.FriendsInfo(incoming_requests=user_obj.friend_requests_in,
|
return schemas.FriendsInfo(incoming_requests=user_obj.friend_requests_in,
|
||||||
outgoing_requests=user_obj.friend_requests_out)
|
outgoing_requests=user_obj.friend_requests_out)
|
||||||
|
|
||||||
|
|
||||||
|
return router
|
||||||
# todo: remove friend requests
|
# todo: remove friend requests
|
||||||
# todo: remove friendship
|
# todo: remove friendship
|
||||||
# todo: search user by email
|
# todo: search user by email
|
||||||
|
|
|
@ -20,9 +20,6 @@ class UserInfo(BaseModel):
|
||||||
id: UUID4
|
id: UUID4
|
||||||
email: EmailStr
|
email: EmailStr
|
||||||
|
|
||||||
class Config:
|
|
||||||
orm_mode = True
|
|
||||||
|
|
||||||
|
|
||||||
class Session(SessionBase):
|
class Session(SessionBase):
|
||||||
user: UUID4
|
user: UUID4
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
from fastapi_users import FastAPIUsers, models
|
from .config import JWT_SECRET
|
||||||
from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
|
from .db import Base
|
||||||
from fastapi_users.authentication import JWTAuthentication
|
|
||||||
from config import JWT_SECRET
|
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from db import database, Base
|
from fastapi_users import FastAPIUsers, models
|
||||||
|
from fastapi_users.authentication import JWTAuthentication
|
||||||
|
from fastapi_users.db import SQLAlchemyBaseUserTable, SQLAlchemyUserDatabase
|
||||||
from sqlalchemy.orm import relationship, backref
|
from sqlalchemy.orm import relationship, backref
|
||||||
from sqlalchemy import Integer, Column
|
|
||||||
from fastapi_users.models import BaseUser
|
|
||||||
|
|
||||||
|
|
||||||
class User(models.BaseUser):
|
class User(models.BaseUser):
|
||||||
|
@ -40,36 +38,30 @@ class UserTable(Base, SQLAlchemyBaseUserTable):
|
||||||
secondaryjoin=("UserTable.id == Friendship.friend_id"))
|
secondaryjoin=("UserTable.id == Friendship.friend_id"))
|
||||||
|
|
||||||
|
|
||||||
user_db = SQLAlchemyUserDatabase(UserDB, database, UserTable.__table__)
|
def add_user_routers(app, database):
|
||||||
jwt_authentication = JWTAuthentication(secret=JWT_SECRET,
|
user_db = SQLAlchemyUserDatabase(UserDB, database, UserTable.__table__)
|
||||||
lifetime_seconds=60 * 60 * 8,
|
jwt_authentication = JWTAuthentication(secret=JWT_SECRET,
|
||||||
tokenUrl="auth/jwt/login")
|
lifetime_seconds=60 * 60 * 8,
|
||||||
|
tokenUrl="auth/jwt/login")
|
||||||
|
|
||||||
fastapi_users = FastAPIUsers(
|
fastapi_users = FastAPIUsers(
|
||||||
user_db,
|
user_db,
|
||||||
[jwt_authentication],
|
[jwt_authentication],
|
||||||
User,
|
User,
|
||||||
UserCreate,
|
UserCreate,
|
||||||
UserUpdate,
|
UserUpdate,
|
||||||
UserDB,
|
UserDB,
|
||||||
)
|
)
|
||||||
current_user = fastapi_users.current_user(active=True, verified=True)
|
|
||||||
current_superuser = fastapi_users.current_user(active=True, superuser=True, verified=True)
|
|
||||||
|
|
||||||
|
def on_after_register(user: UserDB, request: Request):
|
||||||
|
print(f"User {user.id} has registered.")
|
||||||
|
|
||||||
def on_after_register(user: UserDB, request: Request):
|
def on_after_forgot_password(user: UserDB, token: str, request: Request):
|
||||||
print(f"User {user.id} has registered.")
|
print(f"User {user.id} has forgot their password. Reset token: {token}")
|
||||||
|
|
||||||
|
def after_verification_request(user: UserDB, token: str, request: Request):
|
||||||
|
print(f"Verification requested for user {user.id}. Verification token: {token}")
|
||||||
|
|
||||||
def on_after_forgot_password(user: UserDB, token: str, request: Request):
|
|
||||||
print(f"User {user.id} has forgot their password. Reset token: {token}")
|
|
||||||
|
|
||||||
|
|
||||||
def after_verification_request(user: UserDB, token: str, request: Request):
|
|
||||||
print(f"Verification requested for user {user.id}. Verification token: {token}")
|
|
||||||
|
|
||||||
|
|
||||||
def add_user_routers(app):
|
|
||||||
app.include_router(fastapi_users.get_auth_router(jwt_authentication),
|
app.include_router(fastapi_users.get_auth_router(jwt_authentication),
|
||||||
prefix="/auth/jwt",
|
prefix="/auth/jwt",
|
||||||
tags=["auth"])
|
tags=["auth"])
|
||||||
|
@ -90,3 +82,4 @@ def add_user_routers(app):
|
||||||
tags=["auth"],
|
tags=["auth"],
|
||||||
)
|
)
|
||||||
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])
|
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])
|
||||||
|
return fastapi_users
|
|
@ -2,8 +2,9 @@ import os
|
||||||
from typing import Any, Generator
|
from typing import Any, Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import databases
|
||||||
from src.db import Base, get_db
|
from src.db import Base, get_db
|
||||||
from src.main import app as _app
|
from src import get_app
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
|
@ -14,9 +15,7 @@ from sqlalchemy.orm import sessionmaker
|
||||||
# database engines
|
# database engines
|
||||||
SQLALCHEMY_DATABASE_URL = os.getenv('TEST_DATABASE_URL', "sqlite://")
|
SQLALCHEMY_DATABASE_URL = os.getenv('TEST_DATABASE_URL', "sqlite://")
|
||||||
|
|
||||||
engine = create_engine(
|
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
|
||||||
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
|
|
||||||
)
|
|
||||||
|
|
||||||
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
@ -26,7 +25,9 @@ def app() -> Generator[FastAPI, Any, None]:
|
||||||
"""
|
"""
|
||||||
Create a fresh database on each test case.
|
Create a fresh database on each test case.
|
||||||
"""
|
"""
|
||||||
|
print(list(Base.metadata.tables.keys()))
|
||||||
Base.metadata.create_all(engine) # Create the tables.
|
Base.metadata.create_all(engine) # Create the tables.
|
||||||
|
_app = get_app(databases.Database(SQLALCHEMY_DATABASE_URL))
|
||||||
yield _app
|
yield _app
|
||||||
Base.metadata.drop_all(engine)
|
Base.metadata.drop_all(engine)
|
||||||
|
|
||||||
|
@ -61,7 +62,6 @@ def client(app: FastAPI, db_session: Session) -> Generator[TestClient, Any, None
|
||||||
Create a new FastAPI TestClient that uses the `db_session` fixture to override
|
Create a new FastAPI TestClient that uses the `db_session` fixture to override
|
||||||
the `get_db` dependency that is injected into routes.
|
the `get_db` dependency that is injected into routes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _get_test_db():
|
def _get_test_db():
|
||||||
try:
|
try:
|
||||||
yield db_session
|
yield db_session
|
||||||
|
|
|
@ -1,11 +1,24 @@
|
||||||
from backend.src.db import DbSession
|
from src.db import DbSession
|
||||||
from src.schemas import Session
|
from src.schemas import Session
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from src.db import DbSession
|
from src.db import DbSession
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
from src.users import UserCreate, User
|
||||||
|
from fastapi.encoders import jsonable_encoder
|
||||||
|
|
||||||
#----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
#----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def test_session_create(app: FastAPI, db_session: DbSession, client: TestClient):
|
# Tests to write
|
||||||
pass
|
# - User Flow: register, verify, login
|
||||||
|
# - create, list, delete session
|
||||||
|
# - friendship: query user by mail, create friend request, accept
|
||||||
|
def test_register_user(app: FastAPI, db_session: DbSession, client: TestClient):
|
||||||
|
req_data = jsonable_encoder(UserCreate(email="test@abc.com", password="password"))
|
||||||
|
response = client.post("/auth/register", json=req_data)
|
||||||
|
print(response.json())
|
||||||
|
resp_user = User(**response.json())
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert resp_user.is_active
|
||||||
|
assert not resp_user.is_superuser
|
||||||
|
assert not resp_user.is_verified
|
||||||
|
|
Loading…
Reference in New Issue