Skip to content

Commit

Permalink
Merge pull request #9 from FoHoOV/edit-project-permissions
Browse files Browse the repository at this point in the history
feat: edit project permissions per user
  • Loading branch information
FoHoOV authored Mar 18, 2024
2 parents fc93d0f + 1d46829 commit eeb74ca
Show file tree
Hide file tree
Showing 97 changed files with 1,997 additions and 449 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ features it has right now:
2. each project can have many todo categores (with add/edit/delete)
3. each category can have many todo item (with add/edit/delete)
4. each category can be shared among diffrent projects (with detach/attach)
5. each project can be shared with other users (with detach/attach)
5. each project can be shared with other users (with detach/attach, only owners can share with other users)
6. each todo can have many comments (with add/edit/delete)
7. you can sort todo categories or todo items in any arbitrary order and it will be saved on your user account (I'm storing this using linked list)
8. each todo can have multiple tags (with add/edit/delete)
9. you can search (all projects/project specific) by tag
10. adding todo dependencies which also works across projects (with add/delete) - for instance you can't mark a todo as `Done` unless all of its dependencies or dependencies of those dependencies are marked as `Done`
11. creating projects from a default template
12. adding rules to todo categories (currently we only support `MARK_AS_DONE` action meaning when you move a todo item to a todo category, it will be automatically marked as `Done`)
12. adding rules to todo categories (currently we only support `MARK_AS_DONE` and `MARK_AS_UNDONE` action meaning when you move a todo item to a todo category, it will be automatically marked as `Done` or `Undone` depending on what you chose)
13. setting due dates for each todoitem (with add/edit/remove)
14. setting custom permissions per user (PENDING FEATURE: update these permissions, currenly you have to detach and ask the OWNER to invite you with the new permissions)
14. setting custom permissions per user
15. changing project permissions in project settings page (only owners can change permissions)

# demo

Expand All @@ -30,9 +31,8 @@ You can find the demo at [this](https://todos-fohoov.vercel.app/) url (it might
2. goto the frontend project and follow the steps of its README.md

# known bugs
1. conditional rendering (affects NavbarItems but I fixed it with a work around for now): https://github.com/sveltejs/svelte/issues/10321
2. animation bugs(this bug basically destroys my UX but whatever :D): https://github.com/sveltejs/svelte/issues/10493
3. reassignment not causing rerender (affects Alert component dismiss functionality): https://github.com/sveltejs/svelte/issues/10593
1. animation bugs(this bug basically destroys my UX but whatever :D): https://github.com/sveltejs/svelte/issues/10493
2. reassignment not causing rerender (affects Alert component dismiss functionality): https://github.com/sveltejs/svelte/issues/10593
# important

you might need to delete the database after an update. I'll not implement migrations until I'm completely happy with the project. If you are required to delete the db after an update I'll mention it in the release notes (_/ω\_)
Expand Down
10 changes: 7 additions & 3 deletions backend/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from re import I
from fastapi import FastAPI, Request
import typing
from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.routing import APIRoute
Expand All @@ -16,10 +17,13 @@
from error.exceptions import UserFriendlyError


def db_excepted_exception_handler(request: Request, ex: UserFriendlyError):
def db_excepted_exception_handler(request: Request, ex: Exception):
return JSONResponse(
status_code=400,
content={"code": ex.code, "message": ex.description},
content={
"code": typing.cast(UserFriendlyError, ex).code,
"message": typing.cast(UserFriendlyError, ex).description,
},
)


Expand Down
76 changes: 38 additions & 38 deletions backend/api/conftest.py
Original file line number Diff line number Diff line change
@@ -1,77 +1,77 @@
from typing import List, TypedDict


from fastapi import FastAPI
from fastapi.testclient import TestClient
import pytest
from api import create_app
from api.dependencies.db import get_db
from api.dependencies.db_test import get_test_db
from db.schemas.user import UserCreate
from db.test import init_db

from db.utils.user_crud import create_user
from db.schemas.user import User
from db.test import init_db, SessionLocalTest


TestUserType = TypedDict("TestUserType", {"username": str, "password": str})
TestUserType = TypedDict("TestUserType", {"id": int, "username": str, "password": str})

_TEST_USERS: List[TestUserType] = [
{"username": "test_username1", "password": "test_password1"},
{"username": "test_username2", "password": "test_password2"},
{"username": "test_username3", "password": "test_password3"},
{"id": -1, "username": "test_username1", "password": "test_password1"},
{"id": -1, "username": "test_username2", "password": "test_password2"},
{"id": -1, "username": "test_username3", "password": "test_password3"},
]


@pytest.fixture(scope="session")
def test_app():
"""Create and return a test FastAPI application."""
app = create_app()
app.dependency_overrides[get_db] = get_test_db
return app


@pytest.fixture(scope="session")
def test_client(test_app):
def test_client(test_app: FastAPI):
"""Create a TestClient using the test FastAPI application."""
with TestClient(test_app) as client:
yield client


@pytest.fixture(scope="session")
def test_db():
"""Fixture to provide a database session for testing."""
def test_app():
"""Create and return a test FastAPI application."""
init_db() # Ensure the database is initialized
db = next(
get_test_db()
) # Manually get the first (and only) yield which is the session object
try:
yield db
finally:
db.close() # Ensure the session is closed after the test(s)
app = create_app()

def get_test_db():
db = SessionLocalTest()
try:
yield db
finally:
db.close()

app.dependency_overrides[get_db] = get_test_db
return app


@pytest.fixture(scope="session", autouse=True)
def test_users(test_db):
def test_users(test_client: TestClient, test_app: FastAPI):
"""Create test users in the database, ensuring each user is created only once."""
for user in _TEST_USERS:
create_user(
test_db,
UserCreate.model_validate(
{
"username": user["username"],
"password": user["password"],
"confirm_password": user["password"],
}
),
response = test_client.post(
"/user/signup",
json={
"username": user["username"],
"password": user["password"],
"confirm_password": user["password"],
},
)

assert response.status_code == 200

parsed_user = User.model_validate(response.json(), strict=True)
user["id"] = parsed_user.id

return _TEST_USERS


@pytest.fixture(scope="function")
def access_token_factory(test_app):
def access_token_factory(test_client: TestClient):

def _get_access_token(
user: TestUserType,
) -> str:
response = TestClient(test_app).post(
response = test_client.post(
"/oauth/token",
headers={"Content-Type": "application/x-www-form-urlencoded"},
data={
Expand Down
9 changes: 0 additions & 9 deletions backend/api/dependencies/db_test.py

This file was deleted.

15 changes: 14 additions & 1 deletion backend/api/routes/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

from api.conftest import TestUserType
from db.models.user_project_permission import Permission
from db.schemas.project import Project
from db.schemas.project import (
Project,
ProjectAttachAssociation,
ProjectAttachAssociationResponse,
)
from db.schemas.todo_category import TodoCategory
from db.schemas.todo_item import TodoItem

Expand Down Expand Up @@ -56,6 +60,11 @@ def _create_category(user: TestUserType, project_id: int):
"project_id": project_id,
},
)

assert (
response.status_code == 200
), "category should be created with status = 200"

category = TodoCategory.model_validate(response.json(), strict=True)
return category

Expand Down Expand Up @@ -105,4 +114,8 @@ def _attach_to_user(
attach_to_user_response.status_code == 200
), "Sharing project with permissions failed"

return ProjectAttachAssociationResponse.model_validate(
attach_to_user_response.json(), strict=True
)

return _attach_to_user
23 changes: 23 additions & 0 deletions backend/api/routes/project/project.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import builtins
from typing import Annotated
from fastapi import APIRouter, Depends, Response
from starlette.status import HTTP_200_OK
Expand All @@ -6,13 +7,15 @@
from api.dependencies.oauth import get_current_user
from db.models.user import User
from db.schemas.project import (
PartialUserWithPermission,
Project,
ProjectAttachAssociationResponse,
ProjectCreate,
ProjectDetachAssociation,
ProjectRead,
ProjectAttachAssociation,
ProjectUpdate,
ProjectUpdateUserPermissions,
)
from db.utils import project_crud

Expand Down Expand Up @@ -57,6 +60,26 @@ def detach_from_user(
return Response(status_code=HTTP_200_OK)


@router.patch(path="/update-user-permissions", response_model=PartialUserWithPermission)
def update_permissions(
current_user: Annotated[User, Depends(get_current_user)],
permissions: ProjectUpdateUserPermissions,
db: Session = Depends(get_db),
):
updated_project = project_crud.update_user_permissions(
db, permissions, current_user.id
)

user = builtins.list(
filter(
lambda user: user.id == permissions.user_id,
Project.model_validate(updated_project).users,
)
)[0]

return user


@router.get("/search", response_model=Project)
def search(
current_user: Annotated[User, Depends(get_current_user)],
Expand Down
Loading

0 comments on commit eeb74ca

Please sign in to comment.