diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000000..6cf1d1493d --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,37 @@ +name: Test + +on: + push: + branches: + - master + pull_request: + types: + - opened + - synchronize + +jobs: + + test: + runs-on: ubuntu-latest + defaults: + run: + working-directory: src + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Docker Compose build + run: docker compose build + - name: Docker Compose remove old containers and volumes + run: docker compose down -v --remove-orphans + - name: Docker Compose up + run: docker compose up -d + - name: Docker Compose run tests + run: docker compose exec -T backend bash /app/tests-start.sh + - name: Docker Compose cleanup + run: docker compose down -v --remove-orphans diff --git a/src/backend/app/app/api/api_v1/endpoints/items.py b/src/backend/app/app/api/api_v1/endpoints/items.py index 9e186774c5..9878c09caf 100644 --- a/src/backend/app/app/api/api_v1/endpoints/items.py +++ b/src/backend/app/app/api/api_v1/endpoints/items.py @@ -1,15 +1,15 @@ from typing import Any from fastapi import APIRouter, HTTPException -from sqlmodel import select +from sqlmodel import select, func from app.api.deps import CurrentUser, SessionDep -from app.models import Item, ItemCreate, ItemOut, ItemUpdate, Message +from app.models import Item, ItemCreate, ItemOut, ItemUpdate, Message, ItemsOut router = APIRouter() -@router.get("/", response_model=list[ItemOut]) +@router.get("/", response_model=ItemsOut) def read_items( session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100 ) -> Any: @@ -17,9 +17,12 @@ def read_items( Retrieve items. """ + statment = select(func.count()).select_from(Item) + count = session.exec(statment).one() + if current_user.is_superuser: statement = select(Item).offset(skip).limit(limit) - return session.exec(statement).all() + items = session.exec(statement).all() else: statement = ( select(Item) @@ -27,7 +30,9 @@ def read_items( .offset(skip) .limit(limit) ) - return session.exec(statement).all() + items = session.exec(statement).all() + + return ItemsOut(data=items, count=count) @router.get("/{id}", response_model=ItemOut) diff --git a/src/backend/app/app/api/api_v1/endpoints/users.py b/src/backend/app/app/api/api_v1/endpoints/users.py index f3ddd354a1..0e71f1f854 100644 --- a/src/backend/app/app/api/api_v1/endpoints/users.py +++ b/src/backend/app/app/api/api_v1/endpoints/users.py @@ -1,7 +1,7 @@ from typing import Any, List from fastapi import APIRouter, Depends, HTTPException -from sqlmodel import select +from sqlmodel import select, func from app import crud from app.api.deps import ( @@ -18,6 +18,7 @@ UserCreate, UserCreateOpen, UserOut, + UsersOut, UserUpdate, UserUpdateMe, ) @@ -29,15 +30,20 @@ @router.get( "/", dependencies=[Depends(get_current_active_superuser)], - response_model=List[UserOut], + response_model=UsersOut ) def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any: """ Retrieve users. """ + + statment = select(func.count()).select_from(User) + count = session.exec(statment).one() + statement = select(User).offset(skip).limit(limit) users = session.exec(statement).all() - return users + + return UsersOut(data=users, count=count) @router.post( diff --git a/src/backend/app/app/models.py b/src/backend/app/app/models.py index 9186b58702..5331a96b1f 100644 --- a/src/backend/app/app/models.py +++ b/src/backend/app/app/models.py @@ -51,6 +51,11 @@ class UserOut(UserBase): id: int +class UsersOut(SQLModel): + data: list[UserOut] + count: int + + # Shared properties class ItemBase(SQLModel): title: str @@ -80,6 +85,12 @@ class Item(ItemBase, table=True): # Properties to return via API, id is always required class ItemOut(ItemBase): id: int + owner_id: int + + +class ItemsOut(SQLModel): + data: list[ItemOut] + count: int # Generic message diff --git a/src/backend/app/app/tests/api/api_v1/test_celery.py b/src/backend/app/app/tests/api/api_v1/test_celery.py index 7b10a331f2..80973064ff 100644 --- a/src/backend/app/app/tests/api/api_v1/test_celery.py +++ b/src/backend/app/app/tests/api/api_v1/test_celery.py @@ -8,11 +8,11 @@ def test_celery_worker_test( client: TestClient, superuser_token_headers: Dict[str, str] ) -> None: - data = {"msg": "test"} + data = {"message": "test"} r = client.post( f"{settings.API_V1_STR}/utils/test-celery/", json=data, headers=superuser_token_headers, ) response = r.json() - assert response["msg"] == "Word received" + assert response["message"] == "Word received" diff --git a/src/backend/app/app/tests/api/api_v1/test_users.py b/src/backend/app/app/tests/api/api_v1/test_users.py index ba22bfe969..44d97f6649 100644 --- a/src/backend/app/app/tests/api/api_v1/test_users.py +++ b/src/backend/app/app/tests/api/api_v1/test_users.py @@ -110,6 +110,7 @@ def test_retrieve_users( r = client.get(f"{settings.API_V1_STR}/users/", headers=superuser_token_headers) all_users = r.json() - assert len(all_users) > 1 - for item in all_users: + assert len(all_users["data"]) > 1 + assert "count" in all_users + for item in all_users["data"]: assert "email" in item diff --git a/src/backend/app/pyproject.toml b/src/backend/app/pyproject.toml index a28e91b051..e15178463d 100644 --- a/src/backend/app/pyproject.toml +++ b/src/backend/app/pyproject.toml @@ -23,6 +23,8 @@ python-jose = {extras = ["cryptography"], version = "^3.3.0"} httpx = "^0.25.1" psycopg = {extras = ["binary"], version = "^3.1.13"} sqlmodel = "^0.0.16" +# Pin bcrypt until passlib supports the latest +bcrypt = "4.0.1" [tool.poetry.group.dev.dependencies] mypy = "^1.7.0" diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index 469caed772..256a684d26 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -28,10 +28,22 @@ services: - traefik.http.routers.${STACK_NAME?Variable not set}-traefik-public-http.rule=Host(`${DOMAIN?Variable not set}`) - traefik.http.services.${STACK_NAME?Variable not set}-traefik-public.loadbalancer.server.port=80 + db: + ports: + - "5432:5432" + pgadmin: ports: - "5050:5050" + # Uncomment the section below to be able to debug locally + # queue: + # ports: + # - "5671:5671" + # - "5672:5672" + # - "15672:15672" + # - "15671:15671" + flower: ports: - "5555:5555" @@ -84,14 +96,14 @@ services: - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`old-frontend.localhost.tiangolo.com`) - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 - new-frontend: - build: - context: ./new-frontend - labels: - - traefik.enable=true - - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) - - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 +# new-frontend: +# build: +# context: ./new-frontend +# labels: +# - traefik.enable=true +# - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} +# - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) +# - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 networks: traefik-public: diff --git a/src/docker-compose.yml b/src/docker-compose.yml index 7c8d1f4e26..39cccb0fb8 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -191,16 +191,16 @@ services: - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 - new-frontend: - image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}' - build: - context: ./new-frontend - deploy: - labels: - - traefik.enable=true - - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) - - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 +# new-frontend: +# image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}' +# build: +# context: ./new-frontend +# deploy: +# labels: +# - traefik.enable=true +# - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} +# - traefik.http.routers.${STACK_NAME?Variable not set}-new-frontend-http.rule=PathPrefix(`/`) +# - traefik.http.services.${STACK_NAME?Variable not set}-new-frontend.loadbalancer.server.port=80 volumes: