Local changes from desktop

This commit is contained in:
Martin Bauer 2023-07-16 13:22:34 +02:00
parent 0d34959e08
commit e28ab91935
15 changed files with 1982 additions and 2485 deletions

3
.gitignore vendored
View File

@ -10,4 +10,5 @@ npm-debug.*
web-build/
web-report/
/dist
/venv
venv
__pycache__

View File

@ -1,31 +0,0 @@
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

View File

@ -1,39 +0,0 @@
from .db import database, engine, Base, DbSession
from .users import add_user_routers, User
from fastapi import FastAPI
from .routes import create_api_router
from starlette.requests import Request
def get_app(database) -> FastAPI:
application = FastAPI(title="swimtracker", debug=True, version="0.1")
fastapi_users = add_user_routers(application, database)
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
app = get_app(database)
@app.middleware("http")
async def db_session_middleware(request: Request, call_next):
request.state.db = DbSession()
response = await call_next(request)
request.state.db.close()
return response
@app.on_event("startup")
async def startup() -> None:
print("creating")
Base.metadata.create_all(engine)
if not database.is_connected:
await database.connect()
@app.on_event("shutdown")
async def shutdown() -> None:
if database.is_connected:
await database.disconnect()

View File

@ -1,2 +0,0 @@
DATABASE_URL = "sqlite:///db.sqlite"
JWT_SECRET = "4SmRyfsvG86R9jZQfTshfoDlcxYlueHmkMXJbszp"

View File

@ -1,20 +0,0 @@
import databases
import sqlalchemy
from .config import DATABASE_URL
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
from sqlalchemy.orm import sessionmaker
from starlette.requests import Request
database = databases.Database(DATABASE_URL)
Base: DeclarativeMeta = declarative_base()
engine = sqlalchemy.create_engine(
DATABASE_URL, connect_args={"check_same_thread": False}
)
DbSession = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db(request: Request):
return request.state.db

View File

@ -1,41 +0,0 @@
from .db import Base
from sqlalchemy import Column, Integer, Index, LargeBinary, ForeignKey, and_, or_
class Session(Base):
__tablename__ = "session"
device_id = Column(Integer, primary_key=True)
start_time = Column(Integer, primary_key=True)
data = Column(LargeBinary(1024 * 1024 * 2), nullable=False)
user = Column(ForeignKey("user.id"), nullable=False)
value_right_shift = Column(Integer)
tare_value = Column(Integer)
kg_factor = Column(Integer)
Index('device_id', 'start_time', unique=True)
class FriendRequest(Base):
__tablename__ = "friend_request"
requesting_user = Column(ForeignKey("user.id"), primary_key=True)
receiving_user = Column(ForeignKey("user.id"), primary_key=True)
class Friendship(Base):
__tablename__ = "friendship"
user_id = Column(ForeignKey("user.id"), primary_key=True)
friend_id = Column(ForeignKey("user.id"), primary_key=True)
@staticmethod
def befriend(db, userid1, userid2):
db.add(Friendship(user_id=userid1, friend_id=userid2))
@staticmethod
def are_friends(db, userid1, userid2):
query_filter = or_(
and_(Friendship.user_id == userid1, Friendship.friend_id == userid2),
and_(Friendship.user_id == userid2, Friendship.friend_id == userid1),
)
return db.query(Friendship).filter(query_filter).count() > 0

View File

@ -1,95 +0,0 @@
from . import models
from . import schemas
from .db import get_db
from fastapi import APIRouter, Depends, HTTPException
from fastapi import status
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session as DbSession
from sqlalchemy.orm.exc import NoResultFound
from typing import List
from .users import User, UserTable
import base64
def create_api_router(current_user):
router = APIRouter()
@router.post("/sessions",
response_model=schemas.Session,
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"])
def list_sessions(skip=0,
limit=100,
db: DbSession = Depends(get_db),
user: User = Depends(current_user)):
return db.query(models.Session).filter(models.Session.user == user.id).order_by(
models.Session.start_time.desc()).offset(skip).limit(limit).all()
@router.post("/friends/request_friendship/{user_id}", tags=["friends"])
def create_friend_request(other_user_id: str,
db: DbSession = Depends(get_db),
user: User = Depends(current_user)):
if models.Friendship.are_friends(db, other_user_id, user.id):
raise HTTPException(status.HTTP_406_NOT_ACCEPTABLE, detail="already friends")
FR = models.FriendRequest
friend_request_from_other_user = db.query(FR).filter(FR.requesting_user == other_user_id,
FR.receiving_user == user.id).count()
if friend_request_from_other_user > 0:
raise HTTPException(status.HTTP_406_NOT_ACCEPTABLE,
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"])
def accept_friend_request(other_user_id: str,
db: DbSession = Depends(get_db),
user: User = Depends(current_user)):
FR = models.FriendRequest
try:
friend_request = db.query(FR).filter(FR.requesting_user == other_user_id,
FR.receiving_user == user.id).one()
except NoResultFound:
raise HTTPException(status_code=404, detail="No matching friend request found")
models.Friendship.befriend(db, other_user_id, user.id)
db.delete(friend_request)
db.commit()
return {"msg": "ok"}
@router.get("/friends", tags=["friends"], response_model=schemas.FriendsInfo)
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()
return schemas.FriendsInfo(incoming_requests=user_obj.friend_requests_in,
outgoing_requests=user_obj.friend_requests_out)
return router
# todo: remove friend requests
# todo: remove friendship
# todo: search user by email
# todo: add usernames to users
# todo: search by username

View File

@ -1,37 +0,0 @@
from typing import Optional, List
from pydantic import BaseModel, conint, UUID4
from pydantic.networks import EmailStr
class SessionBase(BaseModel):
device_id: int
start_time: conint(gt=1546297200)
data: str
value_right_shift: Optional[conint(ge=0, le=32)]
tare_value: Optional[conint(ge=0)]
kg_factor: Optional[conint(ge=0)]
class Config:
orm_mode = True
class UserInfo(BaseModel):
id: UUID4
email: EmailStr
class Session(SessionBase):
user: UUID4
class FriendRequestCreate(BaseModel):
other_user_id: int
class FriendsInfo(BaseModel):
incoming_requests: List[UserInfo]
outgoing_requests: List[UserInfo]
class Config:
orm_mode = True

View File

@ -1,85 +0,0 @@
from .config import JWT_SECRET
from .db import Base
from fastapi import Request
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
class User(models.BaseUser):
pass
class UserCreate(models.BaseUserCreate):
pass
class UserUpdate(User, models.BaseUserUpdate):
pass
class UserDB(User, models.BaseUserDB):
pass
class UserTable(Base, SQLAlchemyBaseUserTable):
#id = Column(Integer, primary_key=True)
sessions = relationship("Session")
friend_requests_in = relationship(
"UserTable",
secondary="friend_request",
primaryjoin=("UserTable.id == FriendRequest.receiving_user"),
secondaryjoin=("UserTable.id == FriendRequest.requesting_user"),
backref=backref("friend_requests_out"))
friends = relationship('UserTable',
secondary="friendship",
primaryjoin=("UserTable.id == Friendship.user_id"),
secondaryjoin=("UserTable.id == Friendship.friend_id"))
def add_user_routers(app, database):
user_db = SQLAlchemyUserDatabase(UserDB, database, UserTable.__table__)
jwt_authentication = JWTAuthentication(secret=JWT_SECRET,
lifetime_seconds=60 * 60 * 8,
tokenUrl="auth/jwt/login")
fastapi_users = FastAPIUsers(
user_db,
[jwt_authentication],
User,
UserCreate,
UserUpdate,
UserDB,
)
def on_after_register(user: UserDB, request: Request):
print(f"User {user.id} has registered.")
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}")
app.include_router(fastapi_users.get_auth_router(jwt_authentication),
prefix="/auth/jwt",
tags=["auth"])
app.include_router(fastapi_users.get_register_router(on_after_register),
prefix="/auth",
tags=["auth"])
app.include_router(
fastapi_users.get_reset_password_router(JWT_SECRET,
after_forgot_password=on_after_forgot_password),
prefix="/auth",
tags=["auth"],
)
app.include_router(
fastapi_users.get_verify_router(JWT_SECRET,
after_verification_request=after_verification_request),
prefix="/auth",
tags=["auth"],
)
app.include_router(fastapi_users.get_users_router(), prefix="/users", tags=["users"])
return fastapi_users

View File

@ -1,73 +0,0 @@
import os
from typing import Any, Generator
import pytest
import databases
from src.db import Base, get_db
from src import get_app
from fastapi import FastAPI
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Default to using sqlite in memory for fast tests.
# Can be overridden by environment variable for testing in CI against other
# database engines
SQLALCHEMY_DATABASE_URL = os.getenv('TEST_DATABASE_URL', "sqlite://")
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@pytest.fixture(autouse=True)
def app() -> Generator[FastAPI, Any, None]:
"""
Create a fresh database on each test case.
"""
print(list(Base.metadata.tables.keys()))
Base.metadata.create_all(engine) # Create the tables.
_app = get_app(databases.Database(SQLALCHEMY_DATABASE_URL))
yield _app
Base.metadata.drop_all(engine)
@pytest.fixture
def db_session(app: FastAPI) -> Generator[Session, Any, None]:
"""
Creates a fresh sqlalchemy session for each test that operates in a
transaction. The transaction is rolled back at the end of each test ensuring
a clean state.
"""
# connect to the database
connection = engine.connect()
# begin a non-ORM transaction
transaction = connection.begin()
# bind an individual Session to the connection
session = Session(bind=connection)
yield session # use the session in tests.
session.close()
# rollback - everything that happened with the
# Session above (including calls to commit())
# is rolled back.
transaction.rollback()
# return connection to the Engine
connection.close()
@pytest.fixture()
def client(app: FastAPI, db_session: Session) -> Generator[TestClient, Any, None]:
"""
Create a new FastAPI TestClient that uses the `db_session` fixture to override
the `get_db` dependency that is injected into routes.
"""
def _get_test_db():
try:
yield db_session
finally:
pass
app.dependency_overrides[get_db] = _get_test_db
with TestClient(app) as client:
yield client

View File

@ -1,24 +0,0 @@
from src.db import DbSession
from src.schemas import Session
from fastapi import FastAPI
from src.db import DbSession
from fastapi.testclient import TestClient
from src.users import UserCreate, User
from fastapi.encoders import jsonable_encoder
#----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
# Tests to write
# - 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

61
package-lock.json generated
View File

@ -15,6 +15,7 @@
"expo-keep-awake": "~9.1.2",
"expo-linear-gradient": "~9.1.0",
"expo-localization": "~10.1.0",
"i18n-js": "^3.8.0",
"immutable": "^4.0.0-rc.12",
"moment": "^2.27.0",
"msgpack-lite": "^0.1.26",
@ -23,7 +24,6 @@
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "0.63.4",
"react-native-chart-kit": "^3.13.0",
"react-native-gesture-handler": "~1.10.2",
"react-native-reanimated": "~2.1.0",
"react-native-safe-area-context": "3.2.0",
@ -8573,6 +8573,11 @@
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
},
"node_modules/i18n-js": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/i18n-js/-/i18n-js-3.8.0.tgz",
"integrity": "sha512-hDsGgPuvw/2P+lXSbOafAwspK8Ste8YrwuuUg17W3wEcO1JkQxBlPgsN1t2+852nTnz4YSYTjZc/1nAA2PC/nw=="
},
"node_modules/iconv-lite": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
@ -16090,14 +16095,6 @@
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
},
"node_modules/paths-js": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/paths-js/-/paths-js-0.4.11.tgz",
"integrity": "sha512-3mqcLomDBXOo7Fo+UlaenG6f71bk1ZezPQy2JCmYHy2W2k5VKpP+Jbin9H0bjXynelTbglCqdFhSEkeIkKTYUA==",
"engines": {
"node": ">=0.11.0"
}
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@ -16379,11 +16376,6 @@
"node": ">=10.13.0"
}
},
"node_modules/point-in-polygon": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz",
"integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw=="
},
"node_modules/posix-character-classes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@ -16666,22 +16658,6 @@
"react": "16.13.1"
}
},
"node_modules/react-native-chart-kit": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/react-native-chart-kit/-/react-native-chart-kit-3.13.0.tgz",
"integrity": "sha512-XSWw+1I0A6N6zYy76M+tMswgd5sRjiBFyejGTJ07geKVMrSwXY7sb0WYu9Zjr0ee9u8cUun/PB4D9bGqT9f9Zg==",
"dependencies": {
"lodash": "^4.17.11",
"paths-js": "^0.4.10",
"point-in-polygon": "^1.0.1"
},
"peerDependencies": {
"prop-types": "> 15.6.0",
"react": "> 16.7.0",
"react-native": ">= 0.50.0",
"react-native-svg": "> 6.4.1"
}
},
"node_modules/react-native-gesture-handler": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz",
@ -26655,6 +26631,11 @@
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz",
"integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ=="
},
"i18n-js": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/i18n-js/-/i18n-js-3.8.0.tgz",
"integrity": "sha512-hDsGgPuvw/2P+lXSbOafAwspK8Ste8YrwuuUg17W3wEcO1JkQxBlPgsN1t2+852nTnz4YSYTjZc/1nAA2PC/nw=="
},
"iconv-lite": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
@ -32495,11 +32476,6 @@
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
},
"paths-js": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/paths-js/-/paths-js-0.4.11.tgz",
"integrity": "sha512-3mqcLomDBXOo7Fo+UlaenG6f71bk1ZezPQy2JCmYHy2W2k5VKpP+Jbin9H0bjXynelTbglCqdFhSEkeIkKTYUA=="
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@ -32708,11 +32684,6 @@
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
},
"point-in-polygon": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz",
"integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw=="
},
"posix-character-classes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@ -33232,16 +33203,6 @@
}
}
},
"react-native-chart-kit": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/react-native-chart-kit/-/react-native-chart-kit-3.13.0.tgz",
"integrity": "sha512-XSWw+1I0A6N6zYy76M+tMswgd5sRjiBFyejGTJ07geKVMrSwXY7sb0WYu9Zjr0ee9u8cUun/PB4D9bGqT9f9Zg==",
"requires": {
"lodash": "^4.17.11",
"paths-js": "^0.4.10",
"point-in-polygon": "^1.0.1"
}
},
"react-native-gesture-handler": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz",

View File

@ -34,7 +34,6 @@
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "0.63.4",
"react-native-chart-kit": "^3.13.0",
"react-native-gesture-handler": "~1.10.2",
"react-native-reanimated": "~2.1.0",
"react-native-safe-area-context": "3.2.0",

View File

@ -193,6 +193,7 @@ async function queryDeviceFirmwareVersion(swimTrackerHost) {
async function queryNewestFirmwareVersion() {
const QUERY_URL = "https://swimtracker-update.bauer.tech/VERSION";
const result = await request({ url: QUERY_URL, responseType: "text" });
console.log("newest firmware version, got", result);
return result;
}

3954
yarn.lock

File diff suppressed because it is too large Load Diff