diff --git a/storyboard/api/v1/stories.py b/storyboard/api/v1/stories.py index 030a4b1c..64c43571 100644 --- a/storyboard/api/v1/stories.py +++ b/storyboard/api/v1/stories.py @@ -1,5 +1,5 @@ # Copyright (c) 2013 Mirantis Inc. -# Copyright (c) 2016 Codethink Ltd. +# Copyright (c) 2016, 2019 Codethink Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -37,8 +37,10 @@ from storyboard.api.v1 import validations from storyboard.api.v1 import wmodels from storyboard.common import decorators from storyboard.common import exception as exc +from storyboard.db.api import base as api_base from storyboard.db.api import stories as stories_api from storyboard.db.api import subscriptions as subscription_api +from storyboard.db.api import teams as teams_api from storyboard.db.api import timeline_events as events_api from storyboard.db.api import users as users_api @@ -60,6 +62,133 @@ def create_story_wmodel(story): return story_model +class UsersSubcontroller(rest.RestController): + """Manage Users who can access the Story.""" + + @decorators.db_exceptions + @secure(checks.guest) + @wsme_pecan.wsexpose([wmodels.User], int) + def get(self, story_id): + """Get users with access to a story. + + Example:: + + curl https://my.example.org/api/v1/stories/1/users + + :param story_id: ID of the story to get users for. + """ + story = stories_api.story_get_simple( + story_id, current_user=request.current_user_id) + + if not story: + raise exc.NotFound(_("Story %s not found") % story_id) + + if not story.permissions: + return [] + + permission = story.permissions[0] + users = [api_base._filter_non_public_fields(user, user._public_fields) + for user in permission.users] + return [wmodels.User.from_db_model(user) for user in users] + + @decorators.db_exceptions + @secure(checks.authenticated) + @wsme_pecan.wsexpose(wmodels.User, int, int) + def put(self, story_id, user_id): + """Add a user to a story. + + Example:: + + TODO + + :param story_id: ID of the story to add a user to. + :param user_id: ID of the user. + """ + + stories_api.add_user(story_id, user_id, request.current_user_id) + user = users_api.user_get(user_id) + user = api_base._filter_non_public_fields(user, user._public_fields) + + return wmodels.User.from_db_model(user) + + @decorators.db_exceptions + @secure(checks.authenticated) + @wsme_pecan.wsexpose(None, int, int, status_code=204) + def delete(self, story_id, user_id): + """Delete a user from a team. + + Example:: + + TODO + + :param team_id: An ID of the team. + :param user_id: An ID of the user. + """ + stories_api.delete_user(story_id, user_id, request.current_user_id) + + +class TeamsSubcontroller(rest.RestController): + """Manage Teams who can access the story.""" + + @decorators.db_exceptions + @secure(checks.guest) + @wsme_pecan.wsexpose([wmodels.Team], int) + def get(self, story_id): + """Get users inside a team. + + Example:: + + curl https://my.example.org/api/v1/teams/1/users + + :param team_id: An ID of the team. + """ + story = stories_api.story_get_simple( + story_id, current_user=request.current_user_id) + + if not story: + raise exc.NotFound(_("Story %s not found") % story_id) + + if not story.permissions: + return [] + + permission = story.permissions[0] + return [wmodels.Team.from_db_model(team) for team in permission.teams] + + @decorators.db_exceptions + @secure(checks.authenticated) + @wsme_pecan.wsexpose(wmodels.Team, int, int) + def put(self, story_id, team_id): + """Add a team to a story. + + Example:: + + TODO + + :param story_id: ID of the story to add a team to. + :param team_id: ID of the team. + """ + + stories_api.add_team(story_id, team_id, request.current_user_id) + team = teams_api.team_get(team_id) + + return wmodels.Team.from_db_model(team) + + @decorators.db_exceptions + @secure(checks.authenticated) + @wsme_pecan.wsexpose(None, int, int, status_code=204) + def delete(self, story_id, team_id): + """Delete a team from a story. + + Example:: + + TODO + + :param story_id: ID of the story to remove a team from. + :param team_id: ID of the team. + """ + stories_api.delete_team(story_id, team_id, request.current_user_id) + + class StoriesController(rest.RestController): """Manages operations on stories.""" @@ -364,6 +493,8 @@ class StoriesController(rest.RestController): events = NestedTimeLineEventsController() tasks = TasksNestedController() tags = TagsController() + teams = TeamsSubcontroller() + users = UsersSubcontroller() @decorators.db_exceptions @secure(checks.guest) diff --git a/storyboard/db/api/stories.py b/storyboard/db/api/stories.py index dd07030b..c027b191 100644 --- a/storyboard/db/api/stories.py +++ b/storyboard/db/api/stories.py @@ -17,6 +17,7 @@ import datetime import pytz from sqlalchemy.orm import subqueryload +from wsme.exc import ClientSideError from storyboard._i18n import _ from storyboard.common import exception as exc @@ -454,3 +455,125 @@ def update_permission(story, users, teams, session=None): return api_base.entity_update(models.Permission, permission.id, permission_dict) + + +def add_user(story_id, user_id, current_user=None): + session = api_base.get_session() + + with session.begin(subtransactions=True): + story = story_get_simple( + story_id, session=session, current_user=current_user) + if not story: + raise exc.NotFound(_("Story %s not found") % story_id) + + user = users_api.user_get(user_id, session=session) + if not user: + raise exc.NotFound(_("User %s not found") % user_id) + + if not story.permissions: + create_permission(story, [user], [], session) + return + permission = story.permissions[0] + if user_id in [u.id for u in permission.users]: + raise ClientSideError(_("The User %{user_id}d is already in the " + "permission list for Story " + "%{story_id}d") % + {"user_id": user_id, "story_id": story_id}) + permission.users.append(user) + session.add(permission) + + return story + + +def delete_user(story_id, user_id, current_user=None): + session = api_base.get_session() + + with session.begin(subtransactions=True): + story = story_get_simple( + story_id, session=session, current_user=current_user) + if not story: + raise exc.NotFound(_("Story %s not found") % story_id) + + user = users_api.user_get(user_id, session=session) + if not user: + raise exc.NotFound(_("User %s not found") % user_id) + + if not story.permissions: + raise ClientSideError(_("The User %{user_id}d isn't in the " + "permission list for Story " + "%{story_id}d") % + {"user_id": user_id, "story_id": story_id}) + + permission = story.permissions[0] + if user_id not in [u.id for u in permission.users]: + raise ClientSideError(_("The User %{user_id}d isn't in the " + "permission list for Story " + "%{story_id}d") % + {"user_id": user_id, "story_id": story_id}) + + entry = [u for u in permission.users if u.id == user_id][0] + permission.users.remove(entry) + session.add(permission) + + return story + + +def add_team(story_id, team_id, current_user=None): + session = api_base.get_session() + + with session.begin(subtransactions=True): + story = story_get_simple( + story_id, session=session, current_user=current_user) + if not story: + raise exc.NotFound(_("Story %s not found") % story_id) + + team = teams_api.team_get(team_id, session=session) + if not team: + raise exc.NotFound(_("Team %s not found") % team_id) + + if not story.permissions: + create_permission(story, [], [team], session) + return + permission = story.permissions[0] + if team_id in [t.id for t in permission.teams]: + raise ClientSideError(_("The Team %{team_id}d is already in the " + "permission list for Story " + "%{story_id}d") % + {"team_id": team_id, "story_id": story_id}) + permission.teams.append(team) + session.add(permission) + + return story + + +def delete_team(story_id, team_id, current_user=None): + session = api_base.get_session() + + with session.begin(subtransactions=True): + story = story_get_simple( + story_id, session=session, current_user=current_user) + if not story: + raise exc.NotFound(_("Story %s not found") % story_id) + + team = teams_api.team_get(team_id, session=session) + if not team: + raise exc.NotFound(_("User %s not found") % team_id) + + if not story.permissions: + raise ClientSideError(_("The Team %{team_id}d isn't in the " + "permission list for Story " + "%{story_id}d") % + {"team_id": team_id, "story_id": story_id}) + + permission = story.permissions[0] + if team_id not in [t.id for t in permission.teams]: + raise ClientSideError(_("The Team %{team_id}d isn't in the " + "permission list for Story " + "%{story_id}d") % + {"team_id": team_id, "story_id": story_id}) + + entry = [t for t in permission.teams if t.id == team_id][0] + permission.teams.remove(entry) + session.add(permission) + + return story diff --git a/storyboard/db/api/teams.py b/storyboard/db/api/teams.py index b7d79037..3359fd97 100644 --- a/storyboard/db/api/teams.py +++ b/storyboard/db/api/teams.py @@ -34,8 +34,8 @@ def _entity_get(id, session=None): return query.first() -def team_get(team_id): - return _entity_get(team_id) +def team_get(team_id, session=None): + return _entity_get(team_id, session=session) def _team_build_query(project_id=None, **kwargs):