diff --git a/Dockerfile b/Dockerfile index bd6f750..b1ad859 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,9 @@ RUN poetry config virtualenvs.create false WORKDIR /app COPY . /app +ENV PYTHONUNBUFFERED 1 + RUN poetry install --no-interaction --with prod RUN rm -rf /root/.cache/pypoetry -CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:8000", "fishsense_services.app:app" ] \ No newline at end of file +CMD ["gunicorn", "-w", "4", "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000", "fishsense_services.app:app"] diff --git a/README.md b/README.md index 1908ee9..d5bd6b1 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -git clone with --recurse-submodules \ No newline at end of file +git clone with --recurse-submodules +remeber to git submodule update!! \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..970e529 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,62 @@ +version: '3.8' + +services: + db: + container_name: postgres + image: postgres + expose: + - "5432" + environment: + POSTGRES_USER: placeholder_superuser + POSTGRES_PASSWORD: placeholder_pw + PGDATA: /data/postgres + POSTGRES_DB: fishsense_db + volumes: + - db:/data/postgres + ports: + - "5332:5432" + networks: + - fishsense_network + restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -d $$POSTGRES_DB -U $$POSTGRES_USER"] + interval: 30s + timeout: 10s + retries: 5 + + fishsense-database: + build: + context: ./fishsense-database + environment: + DB_HOST: db + DB_NAME: fishsense_db + DB_USER: placeholder_superuser + DB_PASSWORD: placeholder_pw + networks: + - fishsense_network + depends_on: + - db + + fishsense-services: + build: + context: ./ + environment: + DB_HOST: db + DB_NAME: fishsense_db + DB_USER: placeholder_superuser + DB_PASSWORD: placeholder_pw + PYTHONPATH: /app:/app/fishsense-database:/app/fishsense-database/fishsense_database + ports: + - "8000:8000" # fishsense-services port + networks: + - fishsense_network + depends_on: + - db + - fishsense-database + +networks: + fishsense_network: + driver: bridge + +volumes: + db: diff --git a/fishsense-database b/fishsense-database index 71e7c16..30a038e 160000 --- a/fishsense-database +++ b/fishsense-database @@ -1 +1 @@ -Subproject commit 71e7c16a864f7d2b70fe04ec9d903d8940ea689c +Subproject commit 30a038e715f1f8b86362968aeaa73c46ab335f42 diff --git a/fishsense_services/app.py b/fishsense_services/app.py index 21f4d16..53d0740 100644 --- a/fishsense_services/app.py +++ b/fishsense_services/app.py @@ -1,11 +1,15 @@ from fastapi import FastAPI from fishsense_services.routes.usr.login import login_router +from fishsense_services.routes.usr.usr import usr_router +from fishsense_services.routes.img.img import img_router def create_app(): app = FastAPI() app.include_router(login_router, prefix="/login") + app.include_router(usr_router, prefix="/usr") + app.include_router(img_router, prefix="/img") return app diff --git a/fishsense_services/routes/img/img.py b/fishsense_services/routes/img/img.py index 6c80680..cda7d29 100644 --- a/fishsense_services/routes/img/img.py +++ b/fishsense_services/routes/img/img.py @@ -34,7 +34,7 @@ async def upload_img(): raise HTTPException(status_code=404, detail="User does not exist") -@img_router.get("/img/{username}/{img_id}", status_code=200) +@img_router.get("/{username}/{img_id}", status_code=200) async def get_img(username: str, img_id: str): try: @@ -45,7 +45,7 @@ async def get_img(username: str, img_id: str): raise HTTPException(status_code=404, detail=e.args) -@img_router.delete("/img/{username}/{img_name}", status_code=200) +@img_router.delete("/{username}/{img_name}", status_code=200) async def delete_img(username: str, img_name: str): try: @@ -53,13 +53,7 @@ async def delete_img(username: str, img_name: str): return except ValueError as e: - raise HTTPException(status_code=404, detail="User does not exist") - - except FileNotFoundError as e: - raise HTTPException(status_code=404, detail="Image does not exist") - - except Exception as e: - raise HTTPException(status_code=500, detail="Internal server error") + raise HTTPException(status_code=404, detail=e.args) \ No newline at end of file diff --git a/fishsense_services/routes/usr/login.py b/fishsense_services/routes/usr/login.py index b81b805..9a975dd 100644 --- a/fishsense_services/routes/usr/login.py +++ b/fishsense_services/routes/usr/login.py @@ -12,7 +12,7 @@ login_router = APIRouter() -# possible routes: reset passwrod, need to check if there is account associated with it +# possible routes: need to check if there is account associated with it @login_router.post("/api/login") def verify_token(): diff --git a/fishsense_services/routes/usr/usr.py b/fishsense_services/routes/usr/usr.py new file mode 100644 index 0000000..493bbf6 --- /dev/null +++ b/fishsense_services/routes/usr/usr.py @@ -0,0 +1,76 @@ +from fishsense_database.user import create_user, get_user, update_user, delete_user, get_all_users +from fastapi import APIRouter, Request, HTTPException +from fastapi.responses import JSONResponse +from fastapi.encoders import jsonable_encoder + +usr_router = APIRouter() + +@usr_router.post("/create", status_code=200) +async def create_usr(request: Request): + + data = await request.json() + username = data["username"] + email = data["email"] + + try: + id = create_user(username, email) + + if not id: + raise ValueError("User with this username or email already exists.") + + return JSONResponse( + status_code=200, + content={"message": "User created successfully."} + ) + + except ValueError as e: + print(f"ValueError caught in FastAPI: {e}") # Log the error for debugging + return JSONResponse( + status_code=400, + content={"detail": str(e)} # Send the error message as string + ) + + + + +@usr_router.get("/user={username}", status_code=200) +async def get_usr(username: str): + + try: + if not username: + raise ValueError("Must provide a username.") + user = get_user(username) + + if not user: + raise ValueError("User does not exist.") + + return JSONResponse( + status_code=200, + content={"user details": jsonable_encoder(user)} + ) + + except ValueError as e: + + return JSONResponse( + status_code=400, + content={"detail": str(e)} # Send the error message as string + ) + +@usr_router.get("/", status_code=200) +async def all_usrs(): + + try: + users = get_all_users() + + return JSONResponse( + status_code=200, + content={"users": jsonable_encoder(users)} + ) + + except ValueError as e: + + return JSONResponse( + status_code=400, + content={"detail": str(e)} # Send the error message as string + ) + diff --git a/poetry.lock b/poetry.lock index 619b6fa..40cd03a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -31,6 +31,17 @@ doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] trio = ["trio (>=0.26.1)"] +[[package]] +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, +] + [[package]] name = "black" version = "24.10.0" @@ -238,13 +249,13 @@ files = [ [[package]] name = "fastapi" -version = "0.115.3" +version = "0.115.4" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.115.3-py3-none-any.whl", hash = "sha256:8035e8f9a2b0aa89cea03b6c77721178ed5358e1aea4cd8570d9466895c0638c"}, - {file = "fastapi-0.115.3.tar.gz", hash = "sha256:c091c6a35599c036d676fa24bd4a6e19fa30058d93d950216cdc672881f6f7db"}, + {file = "fastapi-0.115.4-py3-none-any.whl", hash = "sha256:0b504a063ffb3cf96a5e27dc1bc32c80ca743a2528574f9cdc77daa2d31b4742"}, + {file = "fastapi-0.115.4.tar.gz", hash = "sha256:db653475586b091cb8b2fec2ac54a680ac6a158e07406e1abae31679e8826349"}, ] [package.dependencies] @@ -256,6 +267,38 @@ typing-extensions = ">=4.8.0" all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=2.11.2)", "python-multipart (>=0.0.7)", "uvicorn[standard] (>=0.12.0)"] +[[package]] +name = "gitdb" +version = "4.0.11" +description = "Git Object Database" +optional = false +python-versions = ">=3.7" +files = [ + {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, + {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, +] + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.43" +description = "GitPython is a Python library used to interact with Git repositories" +optional = false +python-versions = ">=3.7" +files = [ + {file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"}, + {file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"}, +] + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[package.extras] +doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"] +test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] + [[package]] name = "google-auth" version = "2.35.0" @@ -374,6 +417,24 @@ docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-a test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.11.2)"] +[[package]] +name = "psycopg2" +version = "2.9.10" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "psycopg2-2.9.10-cp310-cp310-win32.whl", hash = "sha256:5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716"}, + {file = "psycopg2-2.9.10-cp310-cp310-win_amd64.whl", hash = "sha256:c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a"}, + {file = "psycopg2-2.9.10-cp311-cp311-win32.whl", hash = "sha256:47c4f9875125344f4c2b870e41b6aad585901318068acd01de93f3677a6522c2"}, + {file = "psycopg2-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4"}, + {file = "psycopg2-2.9.10-cp312-cp312-win32.whl", hash = "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067"}, + {file = "psycopg2-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e"}, + {file = "psycopg2-2.9.10-cp39-cp39-win32.whl", hash = "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b"}, + {file = "psycopg2-2.9.10-cp39-cp39-win_amd64.whl", hash = "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442"}, + {file = "psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11"}, +] + [[package]] name = "pyasn1" version = "0.6.1" @@ -558,6 +619,17 @@ files = [ [package.dependencies] pyasn1 = ">=0.1.3" +[[package]] +name = "smmap" +version = "5.0.1" +description = "A pure Python implementation of a sliding window memory map manager" +optional = false +python-versions = ">=3.7" +files = [ + {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, + {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -571,13 +643,13 @@ files = [ [[package]] name = "starlette" -version = "0.41.0" +version = "0.41.2" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - {file = "starlette-0.41.0-py3-none-any.whl", hash = "sha256:a0193a3c413ebc9c78bff1c3546a45bb8c8bcb4a84cae8747d650a65bd37210a"}, - {file = "starlette-0.41.0.tar.gz", hash = "sha256:39cbd8768b107d68bfe1ff1672b38a2c38b49777de46d2a592841d58e3bf7c2a"}, + {file = "starlette-0.41.2-py3-none-any.whl", hash = "sha256:fbc189474b4731cf30fcef52f18a8d070e3f3b46c6a04c97579e85e6ffca942d"}, + {file = "starlette-0.41.2.tar.gz", hash = "sha256:9834fd799d1a87fd346deb76158668cfa0b0d56f85caefe8268e2d97c3468b62"}, ] [package.dependencies] @@ -635,4 +707,4 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "3feae7490dc621656e77bc4afa685bde8df64b889f6bc1243128614bfa62aebe" +content-hash = "198672c47bab0293240c65852a21cb18a34384a977fc750fc4db3df709feefc5" diff --git a/pyproject.toml b/pyproject.toml index 3bdbec8..9659e94 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,9 @@ fastapi = "^0.115.3" google-auth = "^2.35.0" requests = "^2.32.3" uvicorn = "^0.32.0" +psycopg2 = "^2.9.10" +backoff = "^2.2.1" +gitpython = "^3.1.43" [tool.poetry.group.dev.dependencies] black = "^24.3.0"