Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
286dce2
feat: Extended workspace v1 API
michael-pisman Nov 30, 2023
2f19afd
refactor: Removed endpoints to get permissions from v1 API
michael-pisman Nov 30, 2023
38db23c
feat: Added endpoint to get member permissions
michael-pisman Dec 7, 2023
3a2423e
feat: Added function to convert Permission type to string list
michael-pisman Dec 7, 2023
6d3147b
fix: Fixed get_member_permissions
michael-pisman Dec 7, 2023
6f355ea
refactor: Removed debug print statement
michael-pisman Dec 7, 2023
a57d69a
Merge pull request #88 from unipoll/main
michael-pisman Dec 8, 2023
ff077e4
feat: Added owner policy when creating a poll
michael-pisman Dec 9, 2023
5958e08
fix: Fixed query parameters for get_poll
michael-pisman Dec 9, 2023
37c1925
refactor: Refactored PollResponse and CreatePollRequest schemas
michael-pisman Dec 9, 2023
af47bb9
feat: Added validator to CreatePollRequest schema
michael-pisman Dec 9, 2023
0fbeb46
feat: Added validators for question
michael-pisman Dec 9, 2023
14309ce
refactor: Added support for checking poll policies
michael-pisman Dec 9, 2023
72f0571
fix: Replaced group exception with poll exception in get_poll
michael-pisman Dec 9, 2023
0846c0f
fix: Fixed updated poll response
michael-pisman Dec 11, 2023
305c904
refactor: Removed redundant query, added polls' permissions
michael-pisman Dec 11, 2023
b5bcb37
refactor: Changed response for get_member_permissions
michael-pisman Dec 13, 2023
044ab45
feat: Added new endpoints
michael-pisman Dec 13, 2023
8d42950
feat: Added endpoints to get member permissions
michael-pisman Dec 14, 2023
e115ba9
refactor: Removed routes to get permissions in group/workspace
michael-pisman Dec 14, 2023
4dfe32e
fix: Fixed issue with convert_permission_to_string
michael-pisman Dec 15, 2023
d6a69b0
fix: Fixed issue with get_polls
michael-pisman Dec 15, 2023
ca8f723
fix: Fixed error when getting list of policies for a poll
michael-pisman Dec 15, 2023
ba22bdc
refactor: Refactor UserNotAuthorized exception
michael-pisman Dec 16, 2023
f6a1ddc
refactor: Refactor get_member_by_account to raise UserNotMember excep…
michael-pisman Dec 16, 2023
0c6013d
refactor: Change exceptions in check_permissions function
michael-pisman Dec 16, 2023
7286478
fix: Update ErrorWhileRemovingMember exception to use Member instead …
michael-pisman Dec 16, 2023
09fd39b
fix: Fix actions output in UserNotAuthorized exception
michael-pisman Dec 16, 2023
cc48200
fix: Improved permission handling when getting poll
michael-pisman Dec 17, 2023
85e5e5b
fix: Updated request body schema to create/update workspace
michael-pisman Dec 17, 2023
f2ca642
refactor: Changed NonUniqueName exception status code to 409
michael-pisman Dec 18, 2023
a40a478
feat: Added include=workspace query parameter to get_group
michael-pisman Dec 18, 2023
4ad4f7b
refactor: Added workspace ID to WorkspaceCreateOutput example
michael-pisman Dec 18, 2023
dcdccbb
style: Remove unnecessary blank line in get_member_by_account
michael-pisman Dec 18, 2023
56928c7
Merge branch 'main' into api_v2
michael-pisman Jul 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/unipoll_api/actions/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from bson import DBRef

from unipoll_api import AccountManager
from unipoll_api.actions import workspace
from unipoll_api.documents import Policy, Workspace, Group, Account
from unipoll_api import actions
from unipoll_api.schemas import GroupSchemas, WorkspaceSchemas
Expand Down Expand Up @@ -81,6 +82,7 @@ async def create_group(workspace: Workspace,
async def get_group(group: Group,
include_members: bool = False,
include_policies: bool = False,
include_workspace: bool = False,
check_permissions: bool = True) -> GroupSchemas.Group:
try:
await Permissions.check_permissions(group.workspace, "get_groups", check_permissions)
Expand All @@ -89,9 +91,9 @@ async def get_group(group: Group,

members = (await actions.MembersActions.get_members(group)).members if include_members else None
policies = (await actions.PolicyActions.get_policies(resource=group)).policies if include_policies else None
workspace = WorkspaceSchemas.Workspace(**group.workspace.model_dump(exclude={"members", # type: ignore
"policies",
"groups"}))
# workspace = (await actions.WorkspaceActions.get_workspace(group.workspace, True, True, True, True)) if include_workspace else None
workspace = WorkspaceSchemas.Workspace(**group.workspace.model_dump(include={'id', 'name', 'description'})) if include_workspace else None

# Return the workspace with the fetched resources
return GroupSchemas.Group(id=group.id,
name=group.name,
Expand Down
5 changes: 4 additions & 1 deletion src/unipoll_api/actions/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ async def get_policies_from_resource(resource: Resource) -> list[Policy]:
except ResourceExceptions.UserNotAuthorized:
print("User not authorized")
account = AccountManager.active_user.get()
member = await get_member_by_account(account, resource)
if resource.get_document_type() == "Poll":
member = await get_member_by_account(account, resource.workspace)
else:
member = await get_member_by_account(account, resource)
for policy in resource.policies:
if policy.policy_holder.ref.id == member.id: # type: ignore
policies.append(policy) # type: ignore
Expand Down
42 changes: 35 additions & 7 deletions src/unipoll_api/actions/poll.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from beanie import WriteRules
from unipoll_api.documents import Poll, Workspace
from unipoll_api.account_manager import active_user
from unipoll_api.documents import Member, Poll, Workspace
from unipoll_api.schemas import PollSchemas, QuestionSchemas, WorkspaceSchemas
from unipoll_api.utils import Permissions
from unipoll_api.exceptions import ResourceExceptions, PollExceptions
from unipoll_api import actions
from unipoll_api import actions, dependencies


async def get_polls(workspace: Workspace | None = None,
Expand All @@ -18,11 +19,15 @@ async def get_polls(workspace: Workspace | None = None,
except ResourceExceptions.UserNotAuthorized:
poll: Poll
for poll in workspace.polls: # type: ignore
try:
polls.append(await get_poll(poll, check_permissions)) # type: ignore
except ResourceExceptions.UserNotAuthorized:
continue

if poll.public:
polls.append(poll)
else:
polls.append(await get_poll(poll, check_permissions)) # type: ignore

poll_list = []
# Build poll list and return the result
for poll in polls: # type: ignore
Expand All @@ -37,6 +42,8 @@ async def create_poll(workspace: Workspace,
# Check if the user has permission to create polls
await Permissions.check_permissions(workspace, "create_polls", check_permissions)

member: Member = await dependencies.get_member_by_account(active_user.get(), workspace)

# Check if poll name is unique
poll: Poll # For type hinting, until Link type is supported
for poll in workspace.polls: # type: ignore
Expand All @@ -55,6 +62,9 @@ async def create_poll(workspace: Workspace,
# Check if poll was created
if not new_poll:
raise PollExceptions.ErrorWhileCreating(new_poll)

# Add the user as the owner of the poll
await new_poll.add_policy(member, Permissions.POLL_ALL_PERMISSIONS)

# Add the poll to the workspace
workspace.polls.append(new_poll) # type: ignore
Expand All @@ -76,10 +86,14 @@ async def get_poll(poll: Poll,
include_policies: bool = False,
check_permissions: bool = True) -> PollSchemas.PollResponse:
if not poll.public:
await Permissions.check_permissions(poll, "get_poll", check_permissions)
# await Permissions.check_permissions(poll, "get_poll", check_permissions)
try:
await Permissions.check_permissions(poll.workspace, "get_polls", check_permissions)
except ResourceExceptions.UserNotAuthorized:
await Permissions.check_permissions(poll, "get_poll", check_permissions)

# Fetch the resources if the user has the required permissions
questions = (await get_poll_questions(poll)).questions if include_questions else None
questions = (await get_poll_questions(poll, False)).questions if include_questions else None
policies = (await actions.PolicyActions.get_policies(resource=poll)).policies if include_policies else None

workspace = WorkspaceSchemas.WorkspaceShort(**poll.workspace.model_dump()) # type: ignore
Expand Down Expand Up @@ -110,7 +124,13 @@ async def get_poll_questions(poll: Poll,
return QuestionSchemas.QuestionList(questions=question_list)


async def update_poll(poll: Poll, data: PollSchemas.UpdatePollRequest) -> PollSchemas.PollResponse:
async def update_poll(poll: Poll, data: PollSchemas.UpdatePollRequest, check_permissions: bool = True) -> PollSchemas.PollResponse:
await Permissions.check_permissions(poll, "update_poll", check_permissions)

# BUG: After updating the poll, the workspace turns into a Link
# HACK: Save the workspace before updating the poll
workspace = WorkspaceSchemas.WorkspaceShort(**poll.workspace.model_dump()) # type: ignore

# Update the poll
if data.name:
poll.name = data.name
Expand All @@ -125,7 +145,15 @@ async def update_poll(poll: Poll, data: PollSchemas.UpdatePollRequest) -> PollSc

# Save the updated poll
await Poll.save(poll)
return await get_poll(poll, include_questions=True)

# Return the workspace with the fetched resources
return PollSchemas.PollResponse(id=poll.id,
name=poll.name,
description=poll.description,
public=poll.public,
published=poll.published,
workspace=workspace,
questions=poll.questions)


async def delete_poll(poll: Poll):
Expand Down
5 changes: 2 additions & 3 deletions src/unipoll_api/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,10 @@ async def get_member_by_account(account: Account, resource: Workspace | Group) -
"""
Returns a member with the given id.
"""

for member in resource.members:
if member.account.id == account.id: # type: ignore
return member # type: ignore
raise Exceptions.ResourceExceptions.ResourceNotFound("member", account.id)
raise Exceptions.ResourceExceptions.UserNotMember(resource, account)


async def websocket_auth(session: Annotated[str | None, Cookie()] = None,
Expand Down Expand Up @@ -103,7 +102,7 @@ async def get_poll(poll_id: ResourceID) -> Poll:
poll = await Poll.get(poll_id, fetch_links=True)
if poll:
return poll
raise Exceptions.GroupExceptions.GroupNotFound(poll_id)
raise Exceptions.PollExceptions.PollNotFound(poll_id)


# Dependency to get a policy by id and verify it exists
Expand Down
8 changes: 4 additions & 4 deletions src/unipoll_api/exceptions/group.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from unipoll_api.documents import ResourceID, Account, Group
from unipoll_api.documents import Member, ResourceID, Account, Group
from unipoll_api.exceptions import resource


Expand All @@ -23,7 +23,7 @@ def __init__(self, group_id: ResourceID):
# Not authorized
class UserNotAuthorized(resource.UserNotAuthorized):
def __init__(self, account: Account, group: Group, action: str):
super().__init__(account, f'group {group.name}', action)
super().__init__(account, group, action)


# Exception for when a Group was not deleted successfully
Expand Down Expand Up @@ -52,5 +52,5 @@ def __init__(self, group: Group, user: Account):

# Error while removing a member
class ErrorWhileRemovingMember(resource.ErrorWhileRemovingMember):
def __init__(self, group: Group, user: Account):
super().__init__(group, user)
def __init__(self, group: Group, member: Member):
super().__init__(group, member)
2 changes: 1 addition & 1 deletion src/unipoll_api/exceptions/poll.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self, poll_id: ResourceID):
# Not authorized
class UserNotAuthorized(resource.UserNotAuthorized):
def __init__(self, account: Account, poll: Poll, action: str):
super().__init__(account, f'poll {poll.name}', action)
super().__init__(account, poll, action)


# Action not found
Expand Down
6 changes: 3 additions & 3 deletions src/unipoll_api/exceptions/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def __init__(self, detail: str):

class NonUniqueName(APIException):
def __init__(self, resource: str, resource_name: str):
super().__init__(code=status.HTTP_400_BAD_REQUEST,
super().__init__(code=status.HTTP_409_CONFLICT,
detail=f"{resource} with name {resource_name} already exists")


Expand All @@ -46,9 +46,9 @@ def __init__(self, resource: str, resource_id: ResourceID):

# Not authorized
class UserNotAuthorized(APIException):
def __init__(self, account: Account, resource: str, action: str = "perform this action"):
def __init__(self, account: Account, resource: Resource, action: str = "perform this action"):
super().__init__(code=status.HTTP_403_FORBIDDEN,
detail=f"User {account.email} is not authorized to {action} in {resource}")
detail=f"User {account.email} is not authorized to {action} in {resource.get_document_type()} {resource.name}")


# Action not found
Expand Down
8 changes: 4 additions & 4 deletions src/unipoll_api/exceptions/workspace.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from unipoll_api.documents import ResourceID, Workspace, Account
from unipoll_api.documents import Member, ResourceID, Workspace, Account
from unipoll_api.exceptions import resource


Expand Down Expand Up @@ -41,7 +41,7 @@ def __init__(self, workspace: Workspace, user: Account):
# Not authorized
class UserNotAuthorized(resource.UserNotAuthorized):
def __init__(self, account: Account, workspace: Workspace, action: str = "perform this action in"):
super().__init__(account, f"workspace {workspace.name}", action)
super().__init__(account, workspace, action)


# Action not found
Expand All @@ -52,5 +52,5 @@ def __init__(self, action: str):

# Error while removing member
class ErrorWhileRemovingMember(resource.ErrorWhileRemovingMember):
def __init__(self, workspace: Workspace, user: Account):
super().__init__(workspace, user)
def __init__(self, workspace: Workspace, member: Member):
super().__init__(workspace, member)
7 changes: 4 additions & 3 deletions src/unipoll_api/routes/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from . import authentication as AuthenticationRoutes
from . import group as GroupRoutes
from . import workspace as WorkspaceRoutes
from . import poll as PollRoutes

# Create main router
router: APIRouter = APIRouter()
Expand All @@ -22,6 +23,6 @@
prefix="/workspaces",
dependencies=[Depends(Dependencies.set_active_user)])
router.include_router(GroupRoutes.router,
prefix="/workspaces/{workspace_id}/groups",
dependencies=[Depends(Dependencies.set_active_user),
Depends(Dependencies.get_workspace)])
dependencies=[Depends(Dependencies.set_active_user)])
router.include_router(PollRoutes.router,
dependencies=[Depends(Dependencies.set_active_user)])
Loading
Loading