|  | @@ -0,0 +1,205 @@
 | 
	
		
			
			|  | 1 | +# coding=utf-8
 | 
	
		
			
			|  | 2 | +import typing
 | 
	
		
			
			|  | 3 | +
 | 
	
		
			
			|  | 4 | +import transaction
 | 
	
		
			
			|  | 5 | +from pyramid.config import Configurator
 | 
	
		
			
			|  | 6 | +from tracim.models.data import UserRoleInWorkspace
 | 
	
		
			
			|  | 7 | +
 | 
	
		
			
			|  | 8 | +try:  # Python 3.5+
 | 
	
		
			
			|  | 9 | +    from http import HTTPStatus
 | 
	
		
			
			|  | 10 | +except ImportError:
 | 
	
		
			
			|  | 11 | +    from http import client as HTTPStatus
 | 
	
		
			
			|  | 12 | +
 | 
	
		
			
			|  | 13 | +from tracim import TracimRequest
 | 
	
		
			
			|  | 14 | +from tracim.extensions import hapic
 | 
	
		
			
			|  | 15 | +from tracim.lib.core.content import ContentApi
 | 
	
		
			
			|  | 16 | +from tracim.views.controllers import Controller
 | 
	
		
			
			|  | 17 | +from tracim.views.core_api.schemas import FileContentSchema
 | 
	
		
			
			|  | 18 | +from tracim.views.core_api.schemas import FileRevisionSchema
 | 
	
		
			
			|  | 19 | +from tracim.views.core_api.schemas import SetContentStatusSchema
 | 
	
		
			
			|  | 20 | +from tracim.views.core_api.schemas import FileModifySchema
 | 
	
		
			
			|  | 21 | +from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
 | 
	
		
			
			|  | 22 | +from tracim.views.core_api.schemas import NoContentSchema
 | 
	
		
			
			|  | 23 | +from tracim.lib.utils.authorization import require_content_types
 | 
	
		
			
			|  | 24 | +from tracim.lib.utils.authorization import require_workspace_role
 | 
	
		
			
			|  | 25 | +from tracim.exceptions import WorkspaceNotFound, ContentTypeNotAllowed
 | 
	
		
			
			|  | 26 | +from tracim.exceptions import InsufficientUserRoleInWorkspace
 | 
	
		
			
			|  | 27 | +from tracim.exceptions import NotAuthenticated
 | 
	
		
			
			|  | 28 | +from tracim.exceptions import AuthenticationFailed
 | 
	
		
			
			|  | 29 | +from tracim.models.context_models import ContentInContext
 | 
	
		
			
			|  | 30 | +from tracim.models.context_models import RevisionInContext
 | 
	
		
			
			|  | 31 | +from tracim.models.contents import ContentTypeLegacy as ContentType
 | 
	
		
			
			|  | 32 | +from tracim.models.contents import file_type
 | 
	
		
			
			|  | 33 | +from tracim.models.revision_protection import new_revision
 | 
	
		
			
			|  | 34 | +
 | 
	
		
			
			|  | 35 | +FILE_ENDPOINTS_TAG = 'Files'
 | 
	
		
			
			|  | 36 | +
 | 
	
		
			
			|  | 37 | +
 | 
	
		
			
			|  | 38 | +class FileController(Controller):
 | 
	
		
			
			|  | 39 | +
 | 
	
		
			
			|  | 40 | +    @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
 | 
	
		
			
			|  | 41 | +    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
 | 
	
		
			
			|  | 42 | +    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
 | 
	
		
			
			|  | 43 | +    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
 | 
	
		
			
			|  | 44 | +    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
 | 
	
		
			
			|  | 45 | +    @hapic.handle_exception(ContentTypeNotAllowed, HTTPStatus.BAD_REQUEST)
 | 
	
		
			
			|  | 46 | +    @require_workspace_role(UserRoleInWorkspace.READER)
 | 
	
		
			
			|  | 47 | +    @require_content_types([file_type])
 | 
	
		
			
			|  | 48 | +    @hapic.input_path(WorkspaceAndContentIdPathSchema())
 | 
	
		
			
			|  | 49 | +    @hapic.output_body(FileContentSchema())
 | 
	
		
			
			|  | 50 | +    def get_file(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
 | 
	
		
			
			|  | 51 | +        """
 | 
	
		
			
			|  | 52 | +        Get thread content
 | 
	
		
			
			|  | 53 | +        """
 | 
	
		
			
			|  | 54 | +        app_config = request.registry.settings['CFG']
 | 
	
		
			
			|  | 55 | +        api = ContentApi(
 | 
	
		
			
			|  | 56 | +            current_user=request.current_user,
 | 
	
		
			
			|  | 57 | +            session=request.dbsession,
 | 
	
		
			
			|  | 58 | +            config=app_config,
 | 
	
		
			
			|  | 59 | +        )
 | 
	
		
			
			|  | 60 | +        content = api.get_one(
 | 
	
		
			
			|  | 61 | +            hapic_data.path.content_id,
 | 
	
		
			
			|  | 62 | +            content_type=ContentType.Any
 | 
	
		
			
			|  | 63 | +        )
 | 
	
		
			
			|  | 64 | +        return api.get_content_in_context(content)
 | 
	
		
			
			|  | 65 | +
 | 
	
		
			
			|  | 66 | +    @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
 | 
	
		
			
			|  | 67 | +    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
 | 
	
		
			
			|  | 68 | +    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
 | 
	
		
			
			|  | 69 | +    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
 | 
	
		
			
			|  | 70 | +    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
 | 
	
		
			
			|  | 71 | +    @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
 | 
	
		
			
			|  | 72 | +    @require_content_types([file_type])
 | 
	
		
			
			|  | 73 | +    @hapic.input_path(WorkspaceAndContentIdPathSchema())
 | 
	
		
			
			|  | 74 | +    @hapic.input_body(FileModifySchema())
 | 
	
		
			
			|  | 75 | +    @hapic.output_body(FileContentSchema())
 | 
	
		
			
			|  | 76 | +    def update_file(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext:  # nopep8
 | 
	
		
			
			|  | 77 | +        """
 | 
	
		
			
			|  | 78 | +        update thread
 | 
	
		
			
			|  | 79 | +        """
 | 
	
		
			
			|  | 80 | +        app_config = request.registry.settings['CFG']
 | 
	
		
			
			|  | 81 | +        api = ContentApi(
 | 
	
		
			
			|  | 82 | +            current_user=request.current_user,
 | 
	
		
			
			|  | 83 | +            session=request.dbsession,
 | 
	
		
			
			|  | 84 | +            config=app_config,
 | 
	
		
			
			|  | 85 | +        )
 | 
	
		
			
			|  | 86 | +        content = api.get_one(
 | 
	
		
			
			|  | 87 | +            hapic_data.path.content_id,
 | 
	
		
			
			|  | 88 | +            content_type=ContentType.Any
 | 
	
		
			
			|  | 89 | +        )
 | 
	
		
			
			|  | 90 | +        with new_revision(
 | 
	
		
			
			|  | 91 | +                session=request.dbsession,
 | 
	
		
			
			|  | 92 | +                tm=transaction.manager,
 | 
	
		
			
			|  | 93 | +                content=content
 | 
	
		
			
			|  | 94 | +        ):
 | 
	
		
			
			|  | 95 | +            api.update_content(
 | 
	
		
			
			|  | 96 | +                item=content,
 | 
	
		
			
			|  | 97 | +                new_label=hapic_data.body.label,
 | 
	
		
			
			|  | 98 | +                new_content=hapic_data.body.raw_content,
 | 
	
		
			
			|  | 99 | +
 | 
	
		
			
			|  | 100 | +            )
 | 
	
		
			
			|  | 101 | +            api.save(content)
 | 
	
		
			
			|  | 102 | +        return api.get_content_in_context(content)
 | 
	
		
			
			|  | 103 | +
 | 
	
		
			
			|  | 104 | +    @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
 | 
	
		
			
			|  | 105 | +    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
 | 
	
		
			
			|  | 106 | +    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
 | 
	
		
			
			|  | 107 | +    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
 | 
	
		
			
			|  | 108 | +    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
 | 
	
		
			
			|  | 109 | +    @require_workspace_role(UserRoleInWorkspace.READER)
 | 
	
		
			
			|  | 110 | +    @require_content_types([file_type])
 | 
	
		
			
			|  | 111 | +    @hapic.input_path(WorkspaceAndContentIdPathSchema())
 | 
	
		
			
			|  | 112 | +    @hapic.output_body(FileRevisionSchema(many=True))
 | 
	
		
			
			|  | 113 | +    def get_file_revisions(
 | 
	
		
			
			|  | 114 | +            self,
 | 
	
		
			
			|  | 115 | +            context,
 | 
	
		
			
			|  | 116 | +            request: TracimRequest,
 | 
	
		
			
			|  | 117 | +            hapic_data=None
 | 
	
		
			
			|  | 118 | +    ) -> typing.List[RevisionInContext]:
 | 
	
		
			
			|  | 119 | +        """
 | 
	
		
			
			|  | 120 | +        get file revisions
 | 
	
		
			
			|  | 121 | +        """
 | 
	
		
			
			|  | 122 | +        app_config = request.registry.settings['CFG']
 | 
	
		
			
			|  | 123 | +        api = ContentApi(
 | 
	
		
			
			|  | 124 | +            current_user=request.current_user,
 | 
	
		
			
			|  | 125 | +            session=request.dbsession,
 | 
	
		
			
			|  | 126 | +            config=app_config,
 | 
	
		
			
			|  | 127 | +        )
 | 
	
		
			
			|  | 128 | +        content = api.get_one(
 | 
	
		
			
			|  | 129 | +            hapic_data.path.content_id,
 | 
	
		
			
			|  | 130 | +            content_type=ContentType.Any
 | 
	
		
			
			|  | 131 | +        )
 | 
	
		
			
			|  | 132 | +        revisions = content.revisions
 | 
	
		
			
			|  | 133 | +        return [
 | 
	
		
			
			|  | 134 | +            api.get_revision_in_context(revision)
 | 
	
		
			
			|  | 135 | +            for revision in revisions
 | 
	
		
			
			|  | 136 | +        ]
 | 
	
		
			
			|  | 137 | +
 | 
	
		
			
			|  | 138 | +    @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
 | 
	
		
			
			|  | 139 | +    @hapic.handle_exception(NotAuthenticated, HTTPStatus.UNAUTHORIZED)
 | 
	
		
			
			|  | 140 | +    @hapic.handle_exception(InsufficientUserRoleInWorkspace, HTTPStatus.FORBIDDEN)
 | 
	
		
			
			|  | 141 | +    @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.FORBIDDEN)
 | 
	
		
			
			|  | 142 | +    @hapic.handle_exception(AuthenticationFailed, HTTPStatus.FORBIDDEN)
 | 
	
		
			
			|  | 143 | +    @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
 | 
	
		
			
			|  | 144 | +    @require_content_types([file_type])
 | 
	
		
			
			|  | 145 | +    @hapic.input_path(WorkspaceAndContentIdPathSchema())
 | 
	
		
			
			|  | 146 | +    @hapic.input_body(SetContentStatusSchema())
 | 
	
		
			
			|  | 147 | +    @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT)  # nopep8
 | 
	
		
			
			|  | 148 | +    def set_file_status(self, context, request: TracimRequest, hapic_data=None) -> None:  # nopep8
 | 
	
		
			
			|  | 149 | +        """
 | 
	
		
			
			|  | 150 | +        set file status
 | 
	
		
			
			|  | 151 | +        """
 | 
	
		
			
			|  | 152 | +        app_config = request.registry.settings['CFG']
 | 
	
		
			
			|  | 153 | +        api = ContentApi(
 | 
	
		
			
			|  | 154 | +            current_user=request.current_user,
 | 
	
		
			
			|  | 155 | +            session=request.dbsession,
 | 
	
		
			
			|  | 156 | +            config=app_config,
 | 
	
		
			
			|  | 157 | +        )
 | 
	
		
			
			|  | 158 | +        content = api.get_one(
 | 
	
		
			
			|  | 159 | +            hapic_data.path.content_id,
 | 
	
		
			
			|  | 160 | +            content_type=ContentType.Any
 | 
	
		
			
			|  | 161 | +        )
 | 
	
		
			
			|  | 162 | +        with new_revision(
 | 
	
		
			
			|  | 163 | +                session=request.dbsession,
 | 
	
		
			
			|  | 164 | +                tm=transaction.manager,
 | 
	
		
			
			|  | 165 | +                content=content
 | 
	
		
			
			|  | 166 | +        ):
 | 
	
		
			
			|  | 167 | +            api.set_status(
 | 
	
		
			
			|  | 168 | +                content,
 | 
	
		
			
			|  | 169 | +                hapic_data.body.status,
 | 
	
		
			
			|  | 170 | +            )
 | 
	
		
			
			|  | 171 | +            api.save(content)
 | 
	
		
			
			|  | 172 | +        return
 | 
	
		
			
			|  | 173 | +
 | 
	
		
			
			|  | 174 | +    def bind(self, configurator: Configurator) -> None:
 | 
	
		
			
			|  | 175 | +        # Get file
 | 
	
		
			
			|  | 176 | +        configurator.add_route(
 | 
	
		
			
			|  | 177 | +            'file',
 | 
	
		
			
			|  | 178 | +            '/workspaces/{workspace_id}/files/{content_id}',
 | 
	
		
			
			|  | 179 | +            request_method='GET'
 | 
	
		
			
			|  | 180 | +        )
 | 
	
		
			
			|  | 181 | +        configurator.add_view(self.get_file, route_name='file')  # nopep8
 | 
	
		
			
			|  | 182 | +
 | 
	
		
			
			|  | 183 | +        # update file
 | 
	
		
			
			|  | 184 | +        configurator.add_route(
 | 
	
		
			
			|  | 185 | +            'update_file',
 | 
	
		
			
			|  | 186 | +            '/workspaces/{workspace_id}/files/{content_id}',
 | 
	
		
			
			|  | 187 | +            request_method='PUT'
 | 
	
		
			
			|  | 188 | +        )  # nopep8
 | 
	
		
			
			|  | 189 | +        configurator.add_view(self.update_file, route_name='update_file')  # nopep8
 | 
	
		
			
			|  | 190 | +
 | 
	
		
			
			|  | 191 | +        # get file revisions
 | 
	
		
			
			|  | 192 | +        configurator.add_route(
 | 
	
		
			
			|  | 193 | +            'file_revisions',
 | 
	
		
			
			|  | 194 | +            '/workspaces/{workspace_id}/files/{content_id}/revisions',  # nopep8
 | 
	
		
			
			|  | 195 | +            request_method='GET'
 | 
	
		
			
			|  | 196 | +        )
 | 
	
		
			
			|  | 197 | +        configurator.add_view(self.get_file_revisions, route_name='file_revisions')  # nopep8
 | 
	
		
			
			|  | 198 | +
 | 
	
		
			
			|  | 199 | +        # get file revisions
 | 
	
		
			
			|  | 200 | +        configurator.add_route(
 | 
	
		
			
			|  | 201 | +            'set_file_status',
 | 
	
		
			
			|  | 202 | +            '/workspaces/{workspace_id}/files/{content_id}/status',  # nopep8
 | 
	
		
			
			|  | 203 | +            request_method='PUT'
 | 
	
		
			
			|  | 204 | +        )
 | 
	
		
			
			|  | 205 | +        configurator.add_view(self.set_file_status, route_name='set_file_status')  # nopep8
 |