class UploadPermissionController(Controller): """ Endpoints for Upload Permission """ @hapic.with_api_doc( tags=[SWAGGER_TAG__WORKSPACE_UPLOAD_PERMISSION_ENDPOINTS]) @hapic.handle_exception(WorkspacePublicUploadDisabledException, HTTPStatus.BAD_REQUEST) @check_right(is_content_manager) @check_right(has_public_upload_enabled) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.input_body(UploadPermissionCreationBodySchema()) @hapic.output_body(UploadPermissionSchema(many=True)) def add_upload_permission( self, context, request: TracimRequest, hapic_data=None) -> typing.List[UploadPermissionInContext]: """ Allow to add permission for upload to external person """ app_config = request.registry.settings["CFG"] # type: CFG api = UploadPermissionLib(current_user=request.current_user, session=request.dbsession, config=app_config) upload_permission = api.add_permission_to_workspace( request.current_workspace, hapic_data.body.emails, hapic_data.body.password, do_notify=app_config.EMAIL__NOTIFICATION__ACTIVATED, ) return api.get_upload_permissions_in_context(upload_permission) @hapic.with_api_doc( tags=[SWAGGER_TAG__WORKSPACE_UPLOAD_PERMISSION_ENDPOINTS]) @hapic.handle_exception(WorkspacePublicUploadDisabledException, HTTPStatus.BAD_REQUEST) @check_right(is_content_manager) @check_right(has_public_upload_enabled) @hapic.input_query(UploadPermissionListQuerySchema()) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(UploadPermissionSchema(many=True)) def get_upload_permission( self, context, request: TracimRequest, hapic_data=None) -> typing.List[UploadPermissionInContext]: """ Get all upload permission of this workspace """ app_config = request.registry.settings["CFG"] # type: CFG api = UploadPermissionLib( current_user=request.current_user, session=request.dbsession, config=app_config, show_disabled=hapic_data.query.show_disabled, ) upload_permissions = api.get_upload_permissions( request.current_workspace) return api.get_upload_permissions_in_context(upload_permissions) @hapic.with_api_doc( tags=[SWAGGER_TAG__WORKSPACE_UPLOAD_PERMISSION_ENDPOINTS]) @hapic.handle_exception(WorkspacePublicUploadDisabledException, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UploadPermissionNotFound, HTTPStatus.BAD_REQUEST) @check_right(is_content_manager) @check_right(has_public_upload_enabled) @hapic.input_path(UploadPermissionIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def disable_upload_permission(self, context, request: TracimRequest, hapic_data=None) -> None: """ remove an upload permission """ app_config = request.registry.settings["CFG"] # type: CFG api = UploadPermissionLib(current_user=request.current_user, session=request.dbsession, config=app_config) api.disable_upload_permission(request.current_workspace, hapic_data.path.upload_permission_id) @hapic.with_api_doc( tags=[SWAGGER_TAG__WORKSPACE_UPLOAD_PERMISSION_ENDPOINTS]) @hapic.handle_exception(WorkspacePublicUploadDisabledException, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UploadPermissionNotFound, HTTPStatus.BAD_REQUEST) @hapic.input_path(UploadPermissionTokenPath()) @hapic.output_body(UploadPermissionPublicInfoSchema()) def guest_upload_info(self, context, request: TracimRequest, hapic_data=None) -> UploadPermissionInContext: """ get somes informations about upload permission """ app_config = request.registry.settings["CFG"] # type: CFG api = UploadPermissionLib(current_user=None, session=request.dbsession, config=app_config) upload_permission = api.get_upload_permission_by_token( upload_permission_token=hapic_data.path.upload_permission_token ) # type: UploadPermission workspace_api = WorkspaceApi(current_user=None, session=request.dbsession, config=app_config) workspace = workspace_api.get_one(upload_permission.workspace_id) workspace_api.check_public_upload_enabled(workspace) return api.get_upload_permission_in_context(upload_permission) @hapic.with_api_doc( tags=[SWAGGER_TAG__WORKSPACE_UPLOAD_PERMISSION_ENDPOINTS]) @hapic.handle_exception(WrongSharePassword, HTTPStatus.FORBIDDEN) @hapic.handle_exception(UploadPermissionNotFound, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(WorkspacePublicUploadDisabledException, HTTPStatus.BAD_REQUEST) @hapic.input_path(UploadPermissionTokenPath()) @hapic.input_body(UploadPermissionPasswordBodySchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def guest_upload_check(self, context, request: TracimRequest, hapic_data=None) -> None: """ check upload password and token validity """ app_config = request.registry.settings["CFG"] # type: CFG api = UploadPermissionLib(current_user=None, session=request.dbsession, config=app_config) upload_permission = api.get_upload_permission_by_token( upload_permission_token=hapic_data.path.upload_permission_token ) # type: UploadPermission # TODO - G.M - 2019-08-01 - verify in access to upload_permission can be granted # we should considered do these check at decorator level workspace_api = WorkspaceApi(current_user=None, session=request.dbsession, config=app_config) workspace = workspace_api.get_one(upload_permission.workspace_id) workspace_api.check_public_upload_enabled(workspace) api.check_password(upload_permission, password=hapic_data.body.password) return @hapic.with_api_doc( tags=[SWAGGER_TAG__WORKSPACE_UPLOAD_PERMISSION_ENDPOINTS]) @hapic.handle_exception(WrongSharePassword, HTTPStatus.FORBIDDEN) @hapic.handle_exception(UploadPermissionNotFound, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(NoFileValidationError, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(WorkspacePublicUploadDisabledException, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(FileSizeOverWorkspaceEmptySpace, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(FileSizeOverOwnerEmptySpace, HTTPStatus.BAD_REQUEST) @hapic.input_path(UploadPermissionTokenPath()) @hapic.input_forms(UploadDataFormSchema()) @hapic.input_files(UploadFileSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def guest_upload_file(self, context, request: TracimRequest, hapic_data=None) -> None: """ upload files as guest """ # TODO - G.M - 2019-08-14 - replace UploadFiles Object hack to proper hapic support # see upload_files = UploadFiles(request, prefix_pattern="file_") # INFO - G.M - 2019-09-03 - check validation of file here, because hapic can't # handle them. verify if almost one file as been given. if len(upload_files.files) < 1: raise NoFileValidationError( "No files given at input, validation failed.") app_config = request.registry.settings["CFG"] # type: CFG api = UploadPermissionLib(current_user=None, session=request.dbsession, config=app_config) upload_permission = api.get_upload_permission_by_token( upload_permission_token=hapic_data.path.upload_permission_token ) # type: UploadPermission # TODO - G.M - 2019-08-01 - verify in access to upload_permission can be granted # we should considered do these check at decorator level workspace_api = WorkspaceApi(current_user=None, session=request.dbsession, config=app_config) workspace = workspace_api.get_one(upload_permission.workspace_id) workspace_api.check_public_upload_enabled(workspace) api.check_password(upload_permission, password=hapic_data.forms.password) content_api = ContentApi(current_user=None, session=request.dbsession, config=app_config) content_api.check_workspace_size_limitation( content_length=request.content_length, workspace=workspace) content_api.check_owner_size_limitation( content_length=request.content_length, workspace=workspace) api.upload_files( upload_permission=upload_permission, uploader_username=hapic_data.forms.username, message=hapic_data.forms.message, files=upload_files.files, do_notify=app_config.EMAIL__NOTIFICATION__ACTIVATED, ) return def bind(self, configurator: Configurator) -> None: # allow external person to upload file configurator.add_route( "add_upload_permission", "/workspaces/{workspace_id}/upload_permissions", request_method="POST", ) configurator.add_view(self.add_upload_permission, route_name="add_upload_permission") configurator.add_route( "get_upload_permissions", "/workspaces/{workspace_id}/upload_permissions", request_method="GET", ) configurator.add_view(self.get_upload_permission, route_name="get_upload_permissions") configurator.add_route( "delete_upload_permission", "/workspaces/{workspace_id}/upload_permissions/{upload_permission_id}", request_method="DELETE", ) configurator.add_view(self.disable_upload_permission, route_name="delete_upload_permission") # public upload api configurator.add_route( "guest_upload_check", "/public/guest-upload/{upload_permission_token}/check", request_method="POST", ) configurator.add_view(self.guest_upload_check, route_name="guest_upload_check") configurator.add_route( "guest_upload_file", "/public/guest-upload/{upload_permission_token}", request_method="POST", ) configurator.add_view(self.guest_upload_file, route_name="guest_upload_file") configurator.add_route( "guest_upload_info", "/public/guest-upload/{upload_permission_token}", request_method="GET", ) configurator.add_view(self.guest_upload_info, route_name="guest_upload_info")
class WorkspaceController(Controller): @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS]) @check_right(can_see_workspace_information) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(WorkspaceSchema()) def workspace(self, context, request: TracimRequest, hapic_data=None): """ Get workspace informations """ app_config = request.registry.settings['CFG'] wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) return wapi.get_workspace_with_context(request.current_workspace) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS]) @check_right(is_administrator) @hapic.output_body(WorkspaceSchema(many=True), ) def workspaces(self, context, request: TracimRequest, hapic_data=None): """ Returns the list of all workspaces. This route is for admin only. Standard users must use their own route: /api/v2/users/me/workspaces """ app_config = request.registry.settings['CFG'] wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) workspaces = wapi.get_all() return [ wapi.get_workspace_with_context(workspace) for workspace in workspaces ] @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @check_right(can_modify_workspace) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.input_body(WorkspaceModifySchema()) @hapic.output_body(WorkspaceSchema()) def update_workspace(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Update a workspace. This route is for trusted users and administrators. Note : a trusted user can only update spaces on which he/she is space manager """ app_config = request.registry.settings['CFG'] wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) wapi.update_workspace( request.current_workspace, label=hapic_data.body.label, description=hapic_data.body.description, save_now=True, ) return wapi.get_workspace_with_context(request.current_workspace) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(WorkspaceLabelAlreadyUsed, HTTPStatus.BAD_REQUEST) @check_right(is_trusted_user) @hapic.input_body(WorkspaceCreationSchema()) @hapic.output_body(WorkspaceSchema()) def create_workspace(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Create a workspace. This route is for trusted users and administrators. """ app_config = request.registry.settings['CFG'] wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) workspace = wapi.create_workspace( label=hapic_data.body.label, description=hapic_data.body.description, save_now=True, ) return wapi.get_workspace_with_context(workspace) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_TRASH_AND_RESTORE_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @check_right(can_delete_workspace) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def delete_workspace(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Delete a workspace. This route is for trusted users and administrators. Note : a trusted user can only delete spaces on which he/she is space manager """ app_config = request.registry.settings['CFG'] wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) wapi.delete(request.current_workspace, flush=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_TRASH_AND_RESTORE_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @check_right(can_delete_workspace) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def undelete_workspace(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Restore a deleted space. Note : a trusted user can only restore spaces on which he/she is space manager """ app_config = request.registry.settings['CFG'] wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, show_deleted=True, ) wapi.undelete(request.current_workspace, flush=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS]) @check_right(can_see_workspace_information) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(WorkspaceMemberSchema(many=True)) def workspaces_members( self, context, request: TracimRequest, hapic_data=None ) -> typing.List[UserRoleWorkspaceInContext]: """ Returns the list of space members with their role, avatar, etc. """ app_config = request.registry.settings['CFG'] rapi = RoleApi( current_user=request.current_user, session=request.dbsession, config=app_config, ) roles = rapi.get_all_for_workspace(request.current_workspace) return [ rapi.get_user_role_workspace_with_context(user_role) for user_role in roles ] @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS]) @check_right(can_see_workspace_information) @hapic.input_path(WorkspaceAndUserIdPathSchema()) @hapic.output_body(WorkspaceMemberSchema()) def workspaces_member_role( self, context, request: TracimRequest, hapic_data=None ) -> UserRoleWorkspaceInContext: """ Returns given space member with its role, avatar, etc. """ app_config = request.registry.settings['CFG'] rapi = RoleApi( current_user=request.current_user, session=request.dbsession, config=app_config, ) role = rapi.get_one( user_id=hapic_data.path.user_id, workspace_id=hapic_data.path.workspace_id, ) return rapi.get_user_role_workspace_with_context(role) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS]) @hapic.handle_exception(UserRoleNotFound, HTTPStatus.BAD_REQUEST) @check_right(can_modify_workspace) @hapic.input_path(WorkspaceAndUserIdPathSchema()) @hapic.input_body(RoleUpdateSchema()) @hapic.output_body(WorkspaceMemberSchema()) def update_workspaces_members_role( self, context, request: TracimRequest, hapic_data=None ) -> UserRoleWorkspaceInContext: """ Update role of the given space member. This feature is for workspace managers, trusted users and administrators. """ app_config = request.registry.settings['CFG'] rapi = RoleApi( current_user=request.current_user, session=request.dbsession, config=app_config, ) role = rapi.get_one( user_id=hapic_data.path.user_id, workspace_id=hapic_data.path.workspace_id, ) workspace_role = WorkspaceRoles.get_role_from_slug(hapic_data.body.role) role = rapi.update_role( role, role_level=workspace_role.level ) return rapi.get_user_role_workspace_with_context(role) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS]) @check_right(can_modify_workspace) @hapic.handle_exception(UserRoleNotFound, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UserCantRemoveHisOwnRoleInWorkspace, HTTPStatus.BAD_REQUEST) # nopep8 @hapic.input_path(WorkspaceAndUserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def delete_workspaces_members_role( self, context, request: TracimRequest, hapic_data=None ) -> None: """ Remove the user from the space. This feature is for workspace managers and administrators. """ app_config = request.registry.settings['CFG'] rapi = RoleApi( current_user=request.current_user, session=request.dbsession, config=app_config, ) rapi.delete_one( user_id=hapic_data.path.user_id, workspace_id=hapic_data.path.workspace_id, ) return @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS]) @hapic.handle_exception(EmailValidationFailed, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UserDoesNotExist, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UserIsNotActive, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UserIsDeleted, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(RoleAlreadyExistError, HTTPStatus.BAD_REQUEST) @check_right(can_modify_workspace) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.input_body(WorkspaceMemberInviteSchema()) @hapic.output_body(WorkspaceMemberCreationSchema()) def create_workspaces_members_role( self, context, request: TracimRequest, hapic_data=None ) -> UserRoleWorkspaceInContext: """ Add a member to this workspace. This feature is for workspace managers and administrators. """ newly_created = False email_sent = False app_config = request.registry.settings['CFG'] # type: CFG rapi = RoleApi( current_user=request.current_user, session=request.dbsession, config=app_config, ) uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config, show_deactivated=True, show_deleted=True, ) try: _, user = uapi.find( user_id=hapic_data.body.user_id, email=hapic_data.body.user_email, public_name=hapic_data.body.user_public_name ) if user.is_deleted: raise UserIsDeleted('This user has been deleted. Unable to invite him.') # nopep8 if not user.is_active: raise UserIsNotActive('This user is not activated. Unable to invite him') # nopep8 except UserDoesNotExist as exc: if not uapi.allowed_to_invite_new_user(hapic_data.body.user_email): raise exc if app_config.NEW_USER_INVITATION_DO_NOTIFY: user = uapi.create_user( auth_type=AuthType.UNKNOWN, email=hapic_data.body.user_email, password=password_generator(), do_notify=True ) if app_config.EMAIL_NOTIFICATION_ACTIVATED and \ app_config.NEW_USER_INVITATION_DO_NOTIFY and \ app_config.EMAIL_NOTIFICATION_PROCESSING_MODE.lower() == 'sync': email_sent = True else: user = uapi.create_user( auth_type=AuthType.UNKNOWN, email=hapic_data.body.user_email, password=None, do_notify=False ) newly_created = True role = rapi.create_one( user=user, workspace=request.current_workspace, role_level=WorkspaceRoles.get_role_from_slug(hapic_data.body.role).level, # nopep8 with_notif=app_config.EMAIl_NOTIFICATION_ENABLED_ON_INVITATION, flush=True, ) return rapi.get_user_role_workspace_with_context( role, newly_created=newly_created, email_sent=email_sent, ) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS]) @check_right(is_reader) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.input_query(FilterContentQuerySchema()) @hapic.output_body(ContentDigestSchema(many=True)) def workspace_content( self, context, request: TracimRequest, hapic_data=None, ) -> typing.List[ContentInContext]: """ return a list of contents of the space. This is NOT the full content list: by default, returned contents are the ones at root level. In order to get contents in a given folder, then use parent_id query filter. You can also show.hide archived/deleted contents. """ app_config = request.registry.settings['CFG'] content_filter = hapic_data.query api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config, show_archived=content_filter.show_archived, show_deleted=content_filter.show_deleted, show_active=content_filter.show_active, ) contents = api.get_all( parent_ids=content_filter.parent_ids, complete_path_to_id=content_filter.complete_path_to_id, workspace=request.current_workspace, content_type=content_filter.content_type or content_type_list.Any_SLUG, label=content_filter.label, order_by_properties=[Content.label] ) contents = [ api.get_content_in_context(content) for content in contents ] return contents @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UnallowedSubContent, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ParentNotFound, HTTPStatus.BAD_REQUEST) @check_right(can_create_content) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.input_body(ContentCreationSchema()) @hapic.output_body(ContentDigestSchema()) def create_generic_empty_content( self, context, request: TracimRequest, hapic_data=None, ) -> ContentInContext: """ Creates a generic empty content. The minimum viable content has a label and a content type. Creating a content generally starts with a request to this endpoint. For specific contents like files, it is recommended to use the dedicated endpoint. This feature is accessible to contributors and higher role only. """ app_config = request.registry.settings['CFG'] creation_data = hapic_data.body api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config ) parent = None if creation_data.parent_id: try: parent = api.get_one(content_id=creation_data.parent_id, content_type=content_type_list.Any_SLUG) # nopep8 except ContentNotFound as exc: raise ParentNotFound( 'Parent with content_id {} not found'.format(creation_data.parent_id) ) from exc content = api.create( label=creation_data.label, content_type_slug=creation_data.content_type, workspace=request.current_workspace, parent=parent, ) api.save(content, ActionDescription.CREATION) content = api.get_content_in_context(content) return content @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS]) @check_right(is_reader) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.FOUND) # nopep8 def get_content_from_workspace( self, context, request: TracimRequest, hapic_data=None, ) -> None: """ Convenient route allowing to get detail about a content without to known routes associated to its content type. This route generate a HTTP 302 with the right url """ app_config = request.registry.settings['CFG'] content = request.current_content content_type = content_type_list.get_one_by_slug(content.type).slug # TODO - G.M - 2018-08-03 - Jsonify redirect response ? raise HTTPFound( "{base_url}workspaces/{workspace_id}/{content_type}s/{content_id}".format( base_url=BASE_API_V2, workspace_id=content.workspace_id, content_type=content_type, content_id=content.content_id, ) ) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS]) @hapic.input_path(ContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.FOUND) # nopep8 def get_content( self, context, request: TracimRequest, hapic_data=None, ) -> None: """ Convenient route allowing to get detail about a content without to known routes associated to its content type. This route generate a HTTP 302 with the right url """ app_config = request.registry.settings['CFG'] api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one( content_id=hapic_data.path['content_id'], content_type=content_type_list.Any_SLUG ) content_type = content_type_list.get_one_by_slug(content.type).slug # TODO - G.M - 2018-08-03 - Jsonify redirect response ? raise HTTPFound( "{base_url}workspaces/{workspace_id}/{content_type}s/{content_id}".format( base_url=BASE_API_V2, workspace_id=content.workspace_id, content_type=content_type, content_id=content.content_id, ) ) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS]) @hapic.handle_exception(WorkspacesDoNotMatch, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UnallowedSubContent, HTTPStatus.BAD_REQUEST) @check_right(can_move_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.input_body(ContentMoveSchema()) @hapic.output_body(ContentDigestSchema()) def move_content( self, context, request: TracimRequest, hapic_data=None, ) -> ContentInContext: """ Move a content to specified new place. This requires to be content manager in both input and output spaces (which may be the same) """ app_config = request.registry.settings['CFG'] path_data = hapic_data.path move_data = hapic_data.body api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one( path_data.content_id, content_type=content_type_list.Any_SLUG ) new_parent = api.get_one( move_data.new_parent_id, content_type=content_type_list.Any_SLUG ) new_workspace = request.candidate_workspace with new_revision( session=request.dbsession, tm=transaction.manager, content=content ): api.move( content, new_parent=new_parent, new_workspace=new_workspace, must_stay_in_same_workspace=False, ) updated_content = api.get_one( path_data.content_id, content_type=content_type_list.Any_SLUG ) return api.get_content_in_context(updated_content) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ALL_TRASH_AND_RESTORE_ENDPOINTS]) # nopep8 @check_right(is_content_manager) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def delete_content( self, context, request: TracimRequest, hapic_data=None, ) -> None: """ Move a content to the trash. After that, the content will be invisible by default. This action requires the user to be a content manager. Note: the content is still accessible but becomes read-only. """ app_config = request.registry.settings['CFG'] path_data = hapic_data.path api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one( path_data.content_id, content_type=content_type_list.Any_SLUG ) with new_revision( session=request.dbsession, tm=transaction.manager, content=content ): api.delete(content) return @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ALL_TRASH_AND_RESTORE_ENDPOINTS]) # nopep8 @check_right(is_content_manager) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def undelete_content( self, context, request: TracimRequest, hapic_data=None, ) -> None: """ Restore a content from the trash. The content will be visible and editable again. """ app_config = request.registry.settings['CFG'] path_data = hapic_data.path api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config, show_deleted=True, show_archived=True, ) content = api.get_one( path_data.content_id, content_type=content_type_list.Any_SLUG ) with new_revision( session=request.dbsession, tm=transaction.manager, content=content ): api.undelete(content) return @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ALL_ARCHIVE_AND_RESTORE_ENDPOINTS]) # nopep8 @check_right(is_content_manager) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def archive_content( self, context, request: TracimRequest, hapic_data=None, ) -> None: """ Archives a content. The content will be invisible but still available. Difference with delete is that optimizing workspace will not delete archived contents This action requires the user to be a content manager. Note: the content is still accessible but becomes read-only. the difference with delete is that optimizing workspace will not delete archived contents """ app_config = request.registry.settings['CFG'] path_data = hapic_data.path api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(path_data.content_id, content_type=content_type_list.Any_SLUG) # nopep8 with new_revision( session=request.dbsession, tm=transaction.manager, content=content ): api.archive(content) return @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ALL_ARCHIVE_AND_RESTORE_ENDPOINTS]) # nopep8 @check_right(is_content_manager) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def unarchive_content( self, context, request: TracimRequest, hapic_data=None, ) -> None: """ Restore a content from archive. The content will be visible and editable again. """ app_config = request.registry.settings['CFG'] path_data = hapic_data.path api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config, show_archived=True, show_deleted=True, ) content = api.get_one( path_data.content_id, content_type=content_type_list.Any_SLUG ) with new_revision( session=request.dbsession, tm=transaction.manager, content=content ): api.unarchive(content) return def bind(self, configurator: Configurator) -> None: """ Create all routes and views using pyramid configurator for this controller """ # Workspaces configurator.add_route('workspaces', '/workspaces', request_method='GET') # nopep8 configurator.add_view(self.workspaces, route_name='workspaces') # Workspace configurator.add_route('workspace', '/workspaces/{workspace_id}', request_method='GET') # nopep8 configurator.add_view(self.workspace, route_name='workspace') # Create workspace configurator.add_route('create_workspace', '/workspaces', request_method='POST') # nopep8 configurator.add_view(self.create_workspace, route_name='create_workspace') # nopep8 # Delete/Undelete workpace configurator.add_route('delete_workspace', '/workspaces/{workspace_id}/trashed', request_method='PUT') # nopep8 configurator.add_view(self.delete_workspace, route_name='delete_workspace') # nopep8 configurator.add_route('undelete_workspace', '/workspaces/{workspace_id}/trashed/restore', request_method='PUT') # nopep8 configurator.add_view(self.undelete_workspace, route_name='undelete_workspace') # nopep8 # Update Workspace configurator.add_route('update_workspace', '/workspaces/{workspace_id}', request_method='PUT') # nopep8 configurator.add_view(self.update_workspace, route_name='update_workspace') # nopep8 # Workspace Members (Roles) configurator.add_route('workspace_members', '/workspaces/{workspace_id}/members', request_method='GET') # nopep8 configurator.add_view(self.workspaces_members, route_name='workspace_members') # nopep8 # Workspace Members (Role) Individual configurator.add_route('workspace_member_role', '/workspaces/{workspace_id}/members/{user_id}', request_method='GET') # nopep8 configurator.add_view(self.workspaces_member_role, route_name='workspace_member_role') # nopep8 # Update Workspace Members roles configurator.add_route('update_workspace_member', '/workspaces/{workspace_id}/members/{user_id}', request_method='PUT') # nopep8 configurator.add_view(self.update_workspaces_members_role, route_name='update_workspace_member') # nopep8 # Create Workspace Members roles configurator.add_route('create_workspace_member', '/workspaces/{workspace_id}/members', request_method='POST') # nopep8 configurator.add_view(self.create_workspaces_members_role, route_name='create_workspace_member') # nopep8 # Delete Workspace Members roles configurator.add_route('delete_workspace_member', '/workspaces/{workspace_id}/members/{user_id}', request_method='DELETE') # nopep8 configurator.add_view(self.delete_workspaces_members_role, route_name='delete_workspace_member') # nopep8 # Workspace Content configurator.add_route('workspace_content', '/workspaces/{workspace_id}/contents', request_method='GET') # nopep8 configurator.add_view(self.workspace_content, route_name='workspace_content') # nopep8 # Create Generic Content configurator.add_route('create_generic_content', '/workspaces/{workspace_id}/contents', request_method='POST') # nopep8 configurator.add_view(self.create_generic_empty_content, route_name='create_generic_content') # nopep8 # Get Content configurator.add_route('get_content', '/contents/{content_id}', request_method='GET') # nopep8 configurator.add_view(self.get_content, route_name='get_content') # Get Content From workspace configurator.add_route('get_content_from_workspace', '/workspaces/{workspace_id}/contents/{content_id}', request_method='GET') # nopep8 configurator.add_view(self.get_content_from_workspace, route_name='get_content_from_workspace') # nopep8 # Move Content configurator.add_route('move_content', '/workspaces/{workspace_id}/contents/{content_id}/move', request_method='PUT') # nopep8 configurator.add_view(self.move_content, route_name='move_content') # nopep8 # Delete/Undelete Content configurator.add_route('delete_content', '/workspaces/{workspace_id}/contents/{content_id}/trashed', request_method='PUT') # nopep8 configurator.add_view(self.delete_content, route_name='delete_content') # nopep8 configurator.add_route('undelete_content', '/workspaces/{workspace_id}/contents/{content_id}/trashed/restore', request_method='PUT') # nopep8 configurator.add_view(self.undelete_content, route_name='undelete_content') # nopep8 # # Archive/Unarchive Content configurator.add_route('archive_content', '/workspaces/{workspace_id}/contents/{content_id}/archived', request_method='PUT') # nopep8 configurator.add_view(self.archive_content, route_name='archive_content') # nopep8 configurator.add_route('unarchive_content', '/workspaces/{workspace_id}/contents/{content_id}/archived/restore', request_method='PUT') # nopep8 configurator.add_view(self.unarchive_content, route_name='unarchive_content') # nopep8
class FileController(Controller): """ Endpoints for File Content """ # File data @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UnallowedSubContent, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ParentNotFound, HTTPStatus.BAD_REQUEST) @check_right(can_create_file) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(ContentDigestSchema()) @hapic.input_forms(FileCreationFormSchema()) @hapic.input_files(SimpleFileSchema()) def create_file(self, context, request: TracimRequest, hapic_data=None): """ Create a file .This will create 2 new revision. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) _file = hapic_data.files.files parent_id = hapic_data.forms.parent_id api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config ) parent = None # type: typing.Optional['Content'] if parent_id: try: parent = api.get_one(content_id=parent_id, content_type=content_type_list.Any_SLUG) except ContentNotFound as exc: raise ParentNotFound( "Parent with content_id {} not found".format(parent_id) ) from exc content = api.create( filename=_file.filename, content_type_slug=FILE_TYPE, workspace=request.current_workspace, parent=parent, ) api.save(content, ActionDescription.CREATION) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.update_file_data( content, new_filename=_file.filename, new_mimetype=_file.type, new_content=_file.file, ) return api.get_content_in_context(content) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_contributor) @check_right(is_file_content) @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST) @hapic.input_path(FilePathSchema()) @hapic.input_files(SimpleFileSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def upload_file(self, context, request: TracimRequest, hapic_data=None): """ Upload a new version of raw file of content. This will create a new revision. Good pratice for filename is filename is `{label}{file_extension}` or `{filename}`. Default filename value is 'raw' (without file extension) or nothing. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) _file = hapic_data.files.files with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.update_file_data( content, new_filename=_file.filename, new_mimetype=_file.type, new_content=_file.file, ) api.save(content) return @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_reader) @check_right(is_file_content) @hapic.input_query(FileQuerySchema()) @hapic.input_path(FilePathSchema()) @hapic.output_file([]) def download_file(self, context, request: TracimRequest, hapic_data=None): """ Download raw file of last revision of content. Good pratice for filename is filename is `{label}{file_extension}` or `{filename}`. Default filename value is 'raw' (without file extension) or nothing. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) try: file = DepotManager.get().get(content.depot_file) except IOError as exc: raise TracimFileNotFound( "file related to revision {} of content {} not found in depot.".format( content.revision_id, content.content_id ) ) from exc filename = hapic_data.path.filename if not filename or filename == "raw": filename = content.file_name return HapicFile( file_object=file, mimetype=file.content_type, filename=filename, as_attachment=hapic_data.query.force_download, content_length=file.content_length, last_modified=content.updated, ) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_reader) @check_right(is_file_content) @hapic.input_query(FileQuerySchema()) @hapic.input_path(FileRevisionPathSchema()) @hapic.output_file([]) def download_revisions_file(self, context, request: TracimRequest, hapic_data=None): """ Download raw file for specific revision of content. Good pratice for filename is filename is `{label}_r{revision_id}{file_extension}`. Default filename value is 'raw' (without file extension) or nothing. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) revision = api.get_one_revision(revision_id=hapic_data.path.revision_id, content=content) try: file = DepotManager.get().get(revision.depot_file) except IOError as exc: raise TracimFileNotFound( "file related to revision {} of content {} not found in depot.".format( revision.revision_id, revision.content_id ) ) from exc filename = hapic_data.path.filename if not filename or filename == "raw": filename = "{label}_r{revision_id}{file_extension}".format( label=revision.file_name, revision_id=revision.revision_id, file_extension=revision.file_extension, ) return HapicFile( file_object=file, mimetype=file.content_type, filename=filename, as_attachment=hapic_data.query.force_download, content_length=file.content_length, last_modified=revision.updated, ) # preview # pdf @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_reader) @check_right(is_file_content) @hapic.handle_exception(TracimUnavailablePreviewType, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST) @hapic.input_query(PageQuerySchema()) @hapic.input_path(FilePathSchema()) @hapic.output_file([]) def preview_pdf(self, context, request: TracimRequest, hapic_data=None): """ Obtain a specific page pdf preview of last revision of content. Good pratice for filename is filename is `{label}_page_{page_number}.pdf`. Default filename value is 'raw' (without file extension) or nothing. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) pdf_preview_path = api.get_pdf_preview_path( content.content_id, content.revision_id, page_number=hapic_data.query.page, file_extension=content.file_extension, ) filename = hapic_data.path.filename if not filename or filename == "raw": filename = "{label}_page_{page_number}.pdf".format( label=content.label, page_number=hapic_data.query.page ) return HapicFile( file_path=pdf_preview_path, filename=filename, as_attachment=hapic_data.query.force_download, ) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_reader) @check_right(is_file_content) @hapic.handle_exception(TracimUnavailablePreviewType, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST) @hapic.input_query(FileQuerySchema()) @hapic.input_path(FilePathSchema()) @hapic.output_file([]) def preview_pdf_full(self, context, request: TracimRequest, hapic_data=None): """ Obtain a full pdf preview (all page) of last revision of content. Good pratice for filename is filename is `{label}.pdf`. Default filename value is 'raw' (without file extension) or nothing. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) pdf_preview_path = api.get_full_pdf_preview_path( content.revision_id, file_extension=content.file_extension ) filename = hapic_data.path.filename if not filename or filename == "raw": filename = "{label}.pdf".format(label=content.label) return HapicFile( file_path=pdf_preview_path, filename=filename, as_attachment=hapic_data.query.force_download, ) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_reader) @check_right(is_file_content) @hapic.handle_exception(TracimUnavailablePreviewType, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST) @hapic.input_path(FileRevisionPathSchema()) @hapic.input_query(FileQuerySchema()) @hapic.output_file([]) def preview_pdf_full_revision(self, context, request: TracimRequest, hapic_data=None): """ Obtain full pdf preview of a specific revision of content. Good pratice for filename is filename is `{label}_r{revision_id}.pdf`. Default filename value is 'raw' (without file extension) or nothing. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) revision = api.get_one_revision(revision_id=hapic_data.path.revision_id, content=content) pdf_preview_path = api.get_full_pdf_preview_path( revision.revision_id, file_extension=revision.file_extension ) filename = hapic_data.path.filename if not filename or filename == "raw": filename = "{label}_r{revision_id}.pdf".format( revision_id=revision.revision_id, label=revision.label ) return HapicFile( file_path=pdf_preview_path, filename=filename, as_attachment=hapic_data.query.force_download, ) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_reader) @check_right(is_file_content) @hapic.handle_exception(TracimUnavailablePreviewType, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST) @hapic.input_path(FileRevisionPathSchema()) @hapic.input_query(PageQuerySchema()) @hapic.output_file([]) def preview_pdf_revision(self, context, request: TracimRequest, hapic_data=None): """ Obtain a specific page pdf preview of a specific revision of content. Good pratice for filename is filename is `{label}_page_{page_number}.pdf`. Default filename value is 'raw' (without file extension) or nothing. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) revision = api.get_one_revision(revision_id=hapic_data.path.revision_id, content=content) pdf_preview_path = api.get_pdf_preview_path( revision.content_id, revision.revision_id, page_number=hapic_data.query.page, file_extension=revision.file_extension, ) filename = hapic_data.path.filename if not filename or filename == "raw": filename = "{label}_page_{page_number}.pdf".format( label=content.label, page_number=hapic_data.query.page ) return HapicFile( file_path=pdf_preview_path, filename=filename, as_attachment=hapic_data.query.force_download, ) # jpg @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_reader) @check_right(is_file_content) @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST) @hapic.input_path(FilePathSchema()) @hapic.input_query(PageQuerySchema()) @hapic.output_file([]) def preview_jpg(self, context, request: TracimRequest, hapic_data=None): """ Obtain normally sized jpg preview of last revision of content. Good pratice for filename is `filename is {label}_page_{page_number}.jpg`. Default filename value is 'raw' (without file extension) or nothing. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) allowed_dim = api.get_jpg_preview_allowed_dim() jpg_preview_path = api.get_jpg_preview_path( content_id=content.content_id, revision_id=content.revision_id, page_number=hapic_data.query.page, file_extension=content.file_extension, width=allowed_dim.dimensions[0].width, height=allowed_dim.dimensions[0].height, ) filename = hapic_data.path.filename if not filename or filename == "raw": filename = "{label}_page_{page_number}.jpg".format( label=content.label, page_number=hapic_data.query.page ) return HapicFile( file_path=jpg_preview_path, filename=filename, as_attachment=hapic_data.query.force_download, ) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_reader) @check_right(is_file_content) @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(PreviewDimNotAllowed, HTTPStatus.BAD_REQUEST) @hapic.input_query(PageQuerySchema()) @hapic.input_path(FilePreviewSizedPathSchema()) @hapic.output_file([]) def sized_preview_jpg(self, context, request: TracimRequest, hapic_data=None): """ Obtain resized jpg preview of last revision of content. Good pratice for filename is filename is `{label}_page_{page_number}_{width}x{height}.jpg`. Default filename value is 'raw' (without file extension) or nothing. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) jpg_preview_path = api.get_jpg_preview_path( content_id=content.content_id, revision_id=content.revision_id, file_extension=content.file_extension, page_number=hapic_data.query.page, height=hapic_data.path.height, width=hapic_data.path.width, ) filename = hapic_data.path.filename if not filename or filename == "raw": filename = "{label}_page_{page_number}_{width}x{height}.jpg".format( label=content.label, page_number=hapic_data.query.page, width=hapic_data.path.width, height=hapic_data.path.height, ) return HapicFile( file_path=jpg_preview_path, filename=filename, as_attachment=hapic_data.query.force_download, ) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_reader) @check_right(is_file_content) @hapic.handle_exception(UnavailablePreview, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(PageOfPreviewNotFound, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(PreviewDimNotAllowed, HTTPStatus.BAD_REQUEST) @hapic.input_path(FileRevisionPreviewSizedPathSchema()) @hapic.input_query(PageQuerySchema()) @hapic.output_file([]) def sized_preview_jpg_revision(self, context, request: TracimRequest, hapic_data=None): """ Obtain resized jpg preview of a specific revision of content. Good pratice for filename is filename is `{label}_r{revision_id}_page_{page_number}_{width}x{height}.jpg`. Default filename value is 'raw' (without file extension) or nothing. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) revision = api.get_one_revision(revision_id=hapic_data.path.revision_id, content=content) jpg_preview_path = api.get_jpg_preview_path( content_id=content.content_id, revision_id=revision.revision_id, page_number=hapic_data.query.page, height=hapic_data.path.height, width=hapic_data.path.width, file_extension=revision.file_extension, ) filename = hapic_data.path.filename if not filename or filename == "raw": filename = "{label}_r{revision_id}_page_{page_number}_{width}x{height}.jpg".format( revision_id=revision.revision_id, label=revision.label, page_number=hapic_data.query.page, width=hapic_data.path.width, height=hapic_data.path.height, ) return HapicFile( file_path=jpg_preview_path, filename=filename, as_attachment=hapic_data.query.force_download, ) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_reader) @check_right(is_file_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(AllowedJpgPreviewDimSchema()) def allowed_dim_preview_jpg(self, context, request: TracimRequest, hapic_data=None): """ Get allowed dimensions of jpg preview. If restricted is true, only those dimensions are strictly accepted. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) return api.get_jpg_preview_allowed_dim() # File infos @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_reader) @check_right(is_file_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(FileContentSchema()) def get_file_infos(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: """ Get thread content """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) return api.get_content_in_context(content) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST) @check_right(is_contributor) @check_right(is_file_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.input_body(FileContentModifySchema()) @hapic.output_body(FileContentSchema()) def update_file_info( self, context, request: TracimRequest, hapic_data=None ) -> ContentInContext: """ update thread """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.update_content( item=content, new_label=hapic_data.body.label, new_content=hapic_data.body.raw_content, ) api.save(content) return api.get_content_in_context(content) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @check_right(is_reader) @check_right(is_file_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(FileRevisionSchema(many=True)) def get_file_revisions( self, context, request: TracimRequest, hapic_data=None ) -> typing.List[RevisionInContext]: """ get file revisions """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) revisions = content.revisions return [api.get_revision_in_context(revision) for revision in revisions] @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FILE_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @check_right(is_contributor) @check_right(is_file_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.input_body(SetContentStatusSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) @hapic.handle_exception(ContentStatusException, HTTPStatus.BAD_REQUEST) def set_file_status(self, context, request: TracimRequest, hapic_data=None) -> None: """ set file status """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) if content.status == request.json_body.get("status"): raise ContentStatusException( "Content id {} already have status {}".format(content.content_id, content.status) ) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.set_status(content, hapic_data.body.status) api.save(content) return def bind(self, configurator: Configurator) -> None: """ Add route to configurator. """ # file info # # Get file info configurator.add_route( "file_info", "/workspaces/{workspace_id}/files/{content_id}", request_method="GET" ) configurator.add_view(self.get_file_infos, route_name="file_info") # update file configurator.add_route( "update_file_info", "/workspaces/{workspace_id}/files/{content_id}", request_method="PUT", ) configurator.add_view(self.update_file_info, route_name="update_file_info") # raw file # # create file configurator.add_route( "create_file", "/workspaces/{workspace_id}/files", request_method="POST" ) configurator.add_view(self.create_file, route_name="create_file") # upload raw file configurator.add_route( "upload_file", "/workspaces/{workspace_id}/files/{content_id}/raw/{filename:[^/]*}", request_method="PUT", ) configurator.add_view(self.upload_file, route_name="upload_file") # download raw file configurator.add_route( "download_file", "/workspaces/{workspace_id}/files/{content_id}/raw/{filename:[^/]*}", request_method="GET", ) configurator.add_view(self.download_file, route_name="download_file") # download raw file of revision configurator.add_route( "download_revision", "/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/raw/{filename:[^/]*}", request_method="GET", ) configurator.add_view(self.download_revisions_file, route_name="download_revision") # previews # # get preview pdf full configurator.add_route( "preview_pdf_full", "/workspaces/{workspace_id}/files/{content_id}/preview/pdf/full/{filename:[^/]*}", request_method="GET", ) configurator.add_view(self.preview_pdf_full, route_name="preview_pdf_full") # get preview pdf configurator.add_route( "preview_pdf", "/workspaces/{workspace_id}/files/{content_id}/preview/pdf/{filename:[^/]*}", request_method="GET", ) configurator.add_view(self.preview_pdf, route_name="preview_pdf") # get preview jpg allowed dims configurator.add_route( "allowed_dim_preview_jpg", "/workspaces/{workspace_id}/files/{content_id}/preview/jpg/allowed_dims", request_method="GET", ) configurator.add_view(self.allowed_dim_preview_jpg, route_name="allowed_dim_preview_jpg") # get preview jpg configurator.add_route( "preview_jpg", "/workspaces/{workspace_id}/files/{content_id}/preview/jpg/{filename:[^/]*}", request_method="GET", ) configurator.add_view(self.preview_jpg, route_name="preview_jpg") # get preview jpg with size configurator.add_route( "sized_preview_jpg", "/workspaces/{workspace_id}/files/{content_id}/preview/jpg/{width}x{height}/{filename:[^/]*}", request_method="GET", ) configurator.add_view(self.sized_preview_jpg, route_name="sized_preview_jpg") # get jpg preview for revision configurator.add_route( "sized_preview_jpg_revision", "/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/preview/jpg/{width}x{height}/{filename:[^/]*}", request_method="GET", ) configurator.add_view( self.sized_preview_jpg_revision, route_name="sized_preview_jpg_revision" ) # get full pdf preview for revision configurator.add_route( "preview_pdf_full_revision", "/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/preview/pdf/full/{filename:[^/]*}", request_method="GET", ) configurator.add_view( self.preview_pdf_full_revision, route_name="preview_pdf_full_revision" ) # get pdf preview for revision configurator.add_route( "preview_pdf_revision", "/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/preview/pdf/{filename:[^/]*}", request_method="GET", ) configurator.add_view(self.preview_pdf_revision, route_name="preview_pdf_revision") # others # # get file revisions configurator.add_route( "file_revisions", "/workspaces/{workspace_id}/files/{content_id}/revisions", request_method="GET", ) configurator.add_view(self.get_file_revisions, route_name="file_revisions") # get file status configurator.add_route( "set_file_status", "/workspaces/{workspace_id}/files/{content_id}/status", request_method="PUT", ) configurator.add_view(self.set_file_status, route_name="set_file_status")
class UserController(Controller): @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(WorkspaceDigestSchema(many=True)) def user_workspace(self, context, request: TracimRequest, hapic_data=None): """ Get list of user workspaces """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspaces = wapi.get_all_for_user(request.candidate_user) return [ wapi.get_workspace_with_context(workspace) for workspace in workspaces ] @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(UserSchema()) def user(self, context, request: TracimRequest, hapic_data=None): """ Get user infos. """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) return uapi.get_user_with_context(request.candidate_user) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @check_right(is_administrator) @hapic.output_body(UserDigestSchema(many=True)) def users(self, context, request: TracimRequest, hapic_data=None): """ Get all users """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) users = uapi.get_all() context_users = [uapi.get_user_with_context(user) for user in users] return context_users @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.input_query(KnownMemberQuerySchema()) @hapic.output_body(UserDigestSchema(many=True)) def known_members(self, context, request: TracimRequest, hapic_data=None): """ Get known users list """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, show_deactivated=False, ) users = uapi.get_known_user( acp=hapic_data.query.acp, exclude_user_ids=hapic_data.query.exclude_user_ids, exclude_workspace_ids=hapic_data.query.exclude_workspace_ids, ) context_users = [uapi.get_user_with_context(user) for user in users] return context_users @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN) @hapic.handle_exception(EmailAlreadyExistInDb, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ExternalAuthUserEmailModificationDisallowed, HTTPStatus.BAD_REQUEST) @check_right(has_personal_access) @hapic.input_body(SetEmailSchema()) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(UserSchema()) def set_user_email(self, context, request: TracimRequest, hapic_data=None): """ Set user Email """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) user = uapi.set_email( request.candidate_user, hapic_data.body.loggedin_user_password, hapic_data.body.email, do_save=True, ) return uapi.get_user_with_context(user) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN) @hapic.handle_exception(PasswordDoNotMatch, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ExternalAuthUserPasswordModificationDisallowed, HTTPStatus.BAD_REQUEST) @check_right(has_personal_access) @hapic.input_body(SetPasswordSchema()) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_user_password(self, context, request: TracimRequest, hapic_data=None): """ Set user password """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) uapi.set_password( request.candidate_user, hapic_data.body.loggedin_user_password, hapic_data.body.new_password, hapic_data.body.new_password2, do_save=True, ) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_body(SetUserInfoSchema()) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(UserSchema()) def set_user_infos(self, context, request: TracimRequest, hapic_data=None): """ Set user info data """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) user = uapi.update( request.candidate_user, auth_type=request.candidate_user.auth_type, name=hapic_data.body.public_name, timezone=hapic_data.body.timezone, lang=hapic_data.body.lang, do_save=True, ) uapi.execute_updated_user_actions(user) return uapi.get_user_with_context(user) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @hapic.handle_exception(EmailAlreadyExistInDb, HTTPStatus.BAD_REQUEST) @check_right(is_administrator) @hapic.input_body(UserCreationSchema()) @hapic.output_body(UserSchema()) def create_user(self, context, request: TracimRequest, hapic_data=None): """ Create new user """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) gapi = GroupApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) groups = [gapi.get_one_with_name(hapic_data.body.profile)] password = hapic_data.body.password if not password and hapic_data.body.email_notification: password = password_generator() user = uapi.create_user( auth_type=AuthType.UNKNOWN, email=hapic_data.body.email, password=password, timezone=hapic_data.body.timezone, lang=hapic_data.body.lang, name=hapic_data.body.public_name, do_notify=hapic_data.body.email_notification, groups=groups, do_save=True, ) uapi.execute_created_user_actions(user) return uapi.get_user_with_context(user) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENABLE_AND_DISABLE_ENDPOINTS]) @check_right(is_administrator) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def enable_user(self, context, request: TracimRequest, hapic_data=None): """ enable user """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) uapi.enable(user=request.candidate_user, do_save=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_TRASH_AND_RESTORE_ENDPOINTS]) @hapic.handle_exception(UserCantDeleteHimself, HTTPStatus.BAD_REQUEST) @check_right(is_administrator) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def delete_user(self, context, request: TracimRequest, hapic_data=None): """ delete user """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) uapi.delete(user=request.candidate_user, do_save=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_TRASH_AND_RESTORE_ENDPOINTS]) @check_right(is_administrator) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def undelete_user(self, context, request: TracimRequest, hapic_data=None): """ undelete user """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, show_deleted=True, ) uapi.undelete(user=request.candidate_user, do_save=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENABLE_AND_DISABLE_ENDPOINTS]) @hapic.handle_exception(UserCantDisableHimself, HTTPStatus.BAD_REQUEST) @check_right(is_administrator) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def disable_user(self, context, request: TracimRequest, hapic_data=None): """ disable user """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) uapi.disable(user=request.candidate_user, do_save=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @hapic.handle_exception(UserCantChangeIsOwnProfile, HTTPStatus.BAD_REQUEST) @check_right(is_administrator) @hapic.input_path(UserIdPathSchema()) @hapic.input_body(SetUserProfileSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_profile(self, context, request: TracimRequest, hapic_data=None): """ set user profile """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) gapi = GroupApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) groups = [gapi.get_one_with_name(hapic_data.body.profile)] uapi.update( user=request.candidate_user, auth_type=request.candidate_user.auth_type, groups=groups, do_save=True, ) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceIdPathSchema()) @hapic.input_query(ActiveContentFilterQuerySchema()) @hapic.output_body(ContentDigestSchema(many=True)) def last_active_content(self, context, request: TracimRequest, hapic_data=None): """ Get last_active_content for user """ app_config = request.registry.settings["CFG"] # type: CFG content_filter = hapic_data.query api = ContentApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = None if hapic_data.path.workspace_id: workspace = wapi.get_one(hapic_data.path.workspace_id) before_content = None if content_filter.before_content_id: before_content = api.get_one( content_id=content_filter.before_content_id, workspace=workspace, content_type=content_type_list.Any_SLUG, ) last_actives = api.get_last_active(workspace=workspace, limit=content_filter.limit or None, before_content=before_content) return [ api.get_content_in_context(content) for content in last_actives ] @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceIdPathSchema()) @hapic.input_query(ContentIdsQuerySchema()) @hapic.output_body(ReadStatusSchema(many=True)) def contents_read_status(self, context, request: TracimRequest, hapic_data=None): """ get user_read status of contents """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = None if hapic_data.path.workspace_id: workspace = wapi.get_one(hapic_data.path.workspace_id) last_actives = api.get_last_active( workspace=workspace, limit=None, before_content=None, content_ids=hapic_data.query.content_ids or None, ) return [ api.get_content_in_context(content) for content in last_actives ] @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_content_as_read(self, context, request: TracimRequest, hapic_data=None): """ set user_read status of content to read """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.candidate_user, session=request.dbsession, config=app_config, ) api.mark_read(request.current_content, do_flush=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_content_as_unread(self, context, request: TracimRequest, hapic_data=None): """ set user_read status of content to unread """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.candidate_user, session=request.dbsession, config=app_config, ) api.mark_unread(request.current_content, do_flush=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_workspace_as_read(self, context, request: TracimRequest, hapic_data=None): """ set user_read status of all content of workspace to read """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.candidate_user, session=request.dbsession, config=app_config, ) api.mark_read__workspace(request.current_workspace) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_NOTIFICATION_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def enable_workspace_notification(self, context, request: TracimRequest, hapic_data=None): """ enable workspace notification """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = wapi.get_one(hapic_data.path.workspace_id) wapi.enable_notifications(request.candidate_user, workspace) wapi.save(workspace) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_NOTIFICATION_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def disable_workspace_notification(self, context, request: TracimRequest, hapic_data=None): """ disable workspace notification """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = wapi.get_one(hapic_data.path.workspace_id) wapi.disable_notifications(request.candidate_user, workspace) wapi.save(workspace) return def bind(self, configurator: Configurator) -> None: """ Create all routes and views using pyramid configurator for this controller """ # user workspace configurator.add_route( "user_workspace", "/users/{user_id:\d+}/workspaces", request_method="GET" # noqa: W605 ) configurator.add_view(self.user_workspace, route_name="user_workspace") # user info configurator.add_route("user", "/users/{user_id:\d+}", request_method="GET") # noqa: W605 configurator.add_view(self.user, route_name="user") # users lists configurator.add_route("users", "/users", request_method="GET") configurator.add_view(self.users, route_name="users") # known members lists configurator.add_route( "known_members", "/users/{user_id:\d+}/known_members", request_method="GET", # noqa: W605 ) configurator.add_view(self.known_members, route_name="known_members") # set user email configurator.add_route("set_user_email", "/users/{user_id:\d+}/email", request_method="PUT") # noqa: W605 configurator.add_view(self.set_user_email, route_name="set_user_email") # set user password configurator.add_route( "set_user_password", "/users/{user_id:\d+}/password", request_method="PUT" # noqa: W605 ) configurator.add_view(self.set_user_password, route_name="set_user_password") # set user_info configurator.add_route("set_user_info", "/users/{user_id:\d+}", request_method="PUT") # noqa: W605 configurator.add_view(self.set_user_infos, route_name="set_user_info") # create user configurator.add_route("create_user", "/users", request_method="POST") configurator.add_view(self.create_user, route_name="create_user") # enable user configurator.add_route("enable_user", "/users/{user_id:\d+}/enabled", request_method="PUT") # noqa: W605 configurator.add_view(self.enable_user, route_name="enable_user") # disable user configurator.add_route( "disable_user", "/users/{user_id:\d+}/disabled", request_method="PUT" # noqa: W605 ) configurator.add_view(self.disable_user, route_name="disable_user") # delete user configurator.add_route("delete_user", "/users/{user_id:\d+}/trashed", request_method="PUT") # noqa: W605 configurator.add_view(self.delete_user, route_name="delete_user") # undelete user configurator.add_route( "undelete_user", "/users/{user_id:\d+}/trashed/restore", request_method="PUT", # noqa: W605 ) configurator.add_view(self.undelete_user, route_name="undelete_user") # set user profile configurator.add_route( "set_user_profile", "/users/{user_id:\d+}/profile", request_method="PUT" # noqa: W605 ) configurator.add_view(self.set_profile, route_name="set_user_profile") # user content configurator.add_route( "contents_read_status", "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/read_status", # noqa: W605 request_method="GET", ) configurator.add_view(self.contents_read_status, route_name="contents_read_status") # last active content for user configurator.add_route( "last_active_content", "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/recently_active", # noqa: W605 request_method="GET", ) configurator.add_view(self.last_active_content, route_name="last_active_content") # set content as read/unread configurator.add_route( "read_content", "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/{content_id}/read", # noqa: W605 request_method="PUT", ) configurator.add_view(self.set_content_as_read, route_name="read_content") configurator.add_route( "unread_content", "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/{content_id}/unread", # noqa: W605 request_method="PUT", ) configurator.add_view(self.set_content_as_unread, route_name="unread_content") # set workspace as read configurator.add_route( "read_workspace", "/users/{user_id:\d+}/workspaces/{workspace_id}/read", # noqa: W605 request_method="PUT", ) configurator.add_view(self.set_workspace_as_read, route_name="read_workspace") # enable workspace notification configurator.add_route( "enable_workspace_notification", "/users/{user_id:\d+}/workspaces/{workspace_id}/notifications/activate", # noqa: W605 request_method="PUT", ) configurator.add_view(self.enable_workspace_notification, route_name="enable_workspace_notification") # enable workspace notification configurator.add_route( "disable_workspace_notification", "/users/{user_id:\d+}/workspaces/{workspace_id}/notifications/deactivate", # noqa: W605 request_method="PUT", ) configurator.add_view(self.disable_workspace_notification, route_name="disable_workspace_notification")
class CommentController(Controller): @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_COMMENT_ENDPOINTS]) @check_right(is_reader) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(CommentSchema(many=True)) def content_comments(self, context, request: TracimRequest, hapic_data=None): """ Get all comments related to a content in asc order (first is the oldest) """ # login = hapic_data.body app_config = request.registry.settings['CFG'] api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) comments = content.get_comments() comments.sort(key=lambda comment: comment.created) return [api.get_content_in_context(comment) for comment in comments] @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_COMMENT_ENDPOINTS]) @hapic.handle_exception(EmptyCommentContentNotAllowed, HTTPStatus.BAD_REQUEST) # nopep8 @check_right(is_contributor) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.input_body(SetCommentSchema()) @hapic.output_body(CommentSchema()) def add_comment(self, context, request: TracimRequest, hapic_data=None): """ Add new comment """ # login = hapic_data.body app_config = request.registry.settings['CFG'] api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) comment = api.create_comment( content.workspace, content, hapic_data.body.raw_content, do_save=True, ) return api.get_content_in_context(comment) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_COMMENT_ENDPOINTS]) @check_right(can_delete_comment) @hapic.input_path(CommentsPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def delete_comment(self, context, request: TracimRequest, hapic_data=None): """ Delete comment """ app_config = request.registry.settings['CFG'] api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.current_user, session=request.dbsession, config=app_config, ) workspace = wapi.get_one(hapic_data.path.workspace_id) parent = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG, workspace=workspace) comment = api.get_one( hapic_data.path.comment_id, content_type=content_type_list.Comment.slug, workspace=workspace, parent=parent, ) with new_revision(session=request.dbsession, tm=transaction.manager, content=comment): api.delete(comment) return def bind(self, configurator: Configurator): # Get comments configurator.add_route( 'content_comments', '/workspaces/{workspace_id}/contents/{content_id}/comments', request_method='GET') configurator.add_view(self.content_comments, route_name='content_comments') # Add comments configurator.add_route( 'add_comment', '/workspaces/{workspace_id}/contents/{content_id}/comments', request_method='POST') # nopep8 configurator.add_view(self.add_comment, route_name='add_comment') # delete comments configurator.add_route( 'delete_comment', '/workspaces/{workspace_id}/contents/{content_id}/comments/{comment_id}', # nopep8 request_method='DELETE') configurator.add_view(self.delete_comment, route_name='delete_comment')
class FolderController(Controller): @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FOLDER_ENDPOINTS]) @require_workspace_role(UserRoleInWorkspace.READER) @require_content_types([FOLDER_TYPE]) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(TextBasedContentSchema()) def get_folder(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: # nopep8 """ Get folder info """ app_config = request.registry.settings['CFG'] api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one( hapic_data.path.content_id, content_type=content_type_list.Any_SLUG ) return api.get_content_in_context(content) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FOLDER_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST) @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR) @require_content_types([FOLDER_TYPE]) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.input_body(FolderContentModifySchema()) @hapic.output_body(TextBasedContentSchema()) def update_folder(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: # nopep8 """ update folder """ app_config = request.registry.settings['CFG'] api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one( hapic_data.path.content_id, content_type=content_type_list.Any_SLUG ) with new_revision( session=request.dbsession, tm=transaction.manager, content=content ): api.update_content( item=content, new_label=hapic_data.body.label, new_content=hapic_data.body.raw_content, ) api.set_allowed_content( content=content, allowed_content_type_slug_list=hapic_data.body.sub_content_types # nopep8 ) api.save(content) return api.get_content_in_context(content) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FOLDER_ENDPOINTS]) @require_workspace_role(UserRoleInWorkspace.READER) @require_content_types([FOLDER_TYPE]) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(TextBasedRevisionSchema(many=True)) def get_folder_revisions( self, context, request: TracimRequest, hapic_data=None ) -> typing.List[RevisionInContext]: """ get folder revisions """ app_config = request.registry.settings['CFG'] api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one( hapic_data.path.content_id, content_type=content_type_list.Any_SLUG ) revisions = content.revisions return [ api.get_revision_in_context(revision) for revision in revisions ] @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_FOLDER_ENDPOINTS]) @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR) @require_content_types([FOLDER_TYPE]) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.input_body(SetContentStatusSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def set_folder_status(self, context, request: TracimRequest, hapic_data=None) -> None: # nopep8 """ set folder status """ app_config = request.registry.settings['CFG'] api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one( hapic_data.path.content_id, content_type=content_type_list.Any_SLUG ) with new_revision( session=request.dbsession, tm=transaction.manager, content=content ): api.set_status( content, hapic_data.body.status, ) api.save(content) return def bind(self, configurator: Configurator) -> None: # Get folder configurator.add_route( 'folder', '/workspaces/{workspace_id}/folders/{content_id}', request_method='GET' ) configurator.add_view(self.get_folder, route_name='folder') # nopep8 # update folder configurator.add_route( 'update_folder', '/workspaces/{workspace_id}/folders/{content_id}', request_method='PUT' ) # nopep8 configurator.add_view(self.update_folder, route_name='update_folder') # nopep8 # get folder revisions configurator.add_route( 'folder_revisions', '/workspaces/{workspace_id}/folders/{content_id}/revisions', # nopep8 request_method='GET' ) configurator.add_view(self.get_folder_revisions, route_name='folder_revisions') # nopep8 # get folder revisions configurator.add_route( 'set_folder_status', '/workspaces/{workspace_id}/folders/{content_id}/status', # nopep8 request_method='PUT' ) configurator.add_view(self.set_folder_status, route_name='set_folder_status') # nopep8
class ThreadController(Controller): @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_THREAD_ENDPOINTS]) @check_right(is_reader) @check_right(is_thread_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(TextBasedContentSchema()) def get_thread(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: """ Get thread content """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) return api.get_content_in_context(content) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_THREAD_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UserNotMemberOfWorkspace, HTTPStatus.BAD_REQUEST) @check_right(is_contributor) @check_right(is_thread_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.input_body(TextBasedContentModifySchema()) @hapic.output_body(TextBasedContentSchema()) def update_thread(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: """ update thread """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.update_content( item=content, new_label=hapic_data.body.label, new_content=hapic_data.body.raw_content, ) api.save(content) api.execute_update_content_actions(content) return api.get_content_in_context(content) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_THREAD_ENDPOINTS]) @check_right(is_reader) @check_right(is_thread_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(TextBasedRevisionSchema(many=True)) def get_thread_revisions( self, context, request: TracimRequest, hapic_data=None ) -> typing.List[RevisionInContext]: """ get thread revisions """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) revisions = content.revisions return [api.get_revision_in_context(revision) for revision in revisions] @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_THREAD_ENDPOINTS]) @check_right(is_contributor) @check_right(is_thread_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.input_body(SetContentStatusSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) @hapic.handle_exception(ContentStatusException, HTTPStatus.BAD_REQUEST) def set_thread_status(self, context, request: TracimRequest, hapic_data=None) -> None: """ set thread status """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) if content.status == request.json_body.get("status"): raise ContentStatusException( "Content id {} already have status {}".format(content.content_id, content.status) ) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.set_status(content, hapic_data.body.status) api.save(content) api.execute_update_content_actions(content) return def bind(self, configurator: Configurator) -> None: # Get thread configurator.add_route( "thread", "/workspaces/{workspace_id}/threads/{content_id}", request_method="GET" ) configurator.add_view(self.get_thread, route_name="thread") # update thread configurator.add_route( "update_thread", "/workspaces/{workspace_id}/threads/{content_id}", request_method="PUT" ) configurator.add_view(self.update_thread, route_name="update_thread") # get thread revisions configurator.add_route( "thread_revisions", "/workspaces/{workspace_id}/threads/{content_id}/revisions", request_method="GET", ) configurator.add_view(self.get_thread_revisions, route_name="thread_revisions") # get thread revisions configurator.add_route( "set_thread_status", "/workspaces/{workspace_id}/threads/{content_id}/status", request_method="PUT", ) configurator.add_view(self.set_thread_status, route_name="set_thread_status")
class UserController(Controller): @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.input_query(UserWorkspaceFilterQuerySchema()) @hapic.output_body(WorkspaceSchema(many=True)) def user_workspace(self, context, request: TracimRequest, hapic_data=None): """ Get list of user workspaces """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspaces = wapi.get_all_for_user( request.candidate_user, include_owned=hapic_data.query.show_owned_workspace, include_with_role=hapic_data.query.show_workspace_with_role, parents_ids=hapic_data.query.parent_ids, ) return [wapi.get_workspace_with_context(workspace) for workspace in workspaces] @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @hapic.handle_exception(WorkspaceNotFound, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(RoleAlreadyExistError, HTTPStatus.BAD_REQUEST) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.input_body(WorkspaceIdSchema()) @hapic.output_body(WorkspaceSchema()) def join_workspace(self, context, request: TracimRequest, hapic_data=None): """ Join a workspace. Only possible for OPEN workspaces. Subscribing to a ON_REQUEST workspace is done through /api/users/<user_id>/workspace_subscriptions. """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = wapi.add_current_user_as_member(workspace_id=hapic_data.body["workspace_id"]) return wapi.get_workspace_with_context(workspace) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(UserSchema()) def user(self, context, request: TracimRequest, hapic_data=None): """ Get user infos. """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) return uapi.get_user_with_context(request.candidate_user) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(UserDiskSpaceSchema()) def user_disk_space(self, context, request: TracimRequest, hapic_data=None): """ Get user space infos. """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) return uapi.get_user_with_context(request.candidate_user) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @check_right(is_administrator) @hapic.output_body(UserDigestSchema(many=True)) def users(self, context, request: TracimRequest, hapic_data=None): """ Get all users """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) users = uapi.get_all() context_users = [uapi.get_user_with_context(user) for user in users] return context_users @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.input_query(KnownMembersQuerySchema()) @hapic.output_body(UserDigestSchema(many=True)) @hapic.handle_exception(CannotUseBothIncludeAndExcludeWorkspaceUsers, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(TooShortAutocompleteString, HTTPStatus.BAD_REQUEST) def known_members(self, context, request: TracimRequest, hapic_data=None): """ Get known users list """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, show_deactivated=False, ) users = uapi.get_known_users( acp=hapic_data.query.acp, exclude_user_ids=hapic_data.query.exclude_user_ids, exclude_workspace_ids=hapic_data.query.exclude_workspace_ids, include_workspace_ids=hapic_data.query.include_workspace_ids, limit=hapic_data.query.limit, filter_results=app_config.KNOWN_MEMBERS__FILTER, ) context_users = [uapi.get_user_with_context(user) for user in users] return context_users @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN) @hapic.handle_exception(EmailAlreadyExists, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ExternalAuthUserEmailModificationDisallowed, HTTPStatus.BAD_REQUEST) @check_right(has_personal_access) @hapic.input_body(SetEmailSchema()) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(UserSchema()) def set_user_email(self, context, request: TracimRequest, hapic_data=None): """ Set user Email """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) user = uapi.set_email( request.candidate_user, hapic_data.body.loggedin_user_password, hapic_data.body.email, do_save=True, ) return uapi.get_user_with_context(user) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN) @hapic.handle_exception(UsernameAlreadyExists, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ReservedUsernameError, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(TracimValidationFailed, HTTPStatus.BAD_REQUEST) @check_right(has_personal_access) @hapic.input_body(SetUsernameSchema()) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(UserSchema()) def set_user_username(self, context, request: TracimRequest, hapic_data=None): """ Set user username """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) user = uapi.set_username( request.candidate_user, hapic_data.body.loggedin_user_password, hapic_data.body.username, do_save=True, ) return uapi.get_user_with_context(user) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN) @hapic.handle_exception(PasswordDoNotMatch, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ExternalAuthUserPasswordModificationDisallowed, HTTPStatus.BAD_REQUEST) @check_right(has_personal_access) @hapic.input_body(SetPasswordSchema()) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_user_password(self, context, request: TracimRequest, hapic_data=None): """ Set user password """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) uapi.set_password( request.candidate_user, hapic_data.body.loggedin_user_password, hapic_data.body.new_password, hapic_data.body.new_password2, do_save=True, ) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_body(SetUserInfoSchema()) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(UserSchema()) def set_user_infos(self, context, request: TracimRequest, hapic_data=None): """ Set user info data """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) user = uapi.update( request.candidate_user, auth_type=request.candidate_user.auth_type, name=hapic_data.body.public_name, timezone=hapic_data.body.timezone, lang=hapic_data.body.lang, do_save=True, ) uapi.execute_updated_user_actions(user) return uapi.get_user_with_context(user) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @hapic.handle_exception(EmailAlreadyExists, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UsernameAlreadyExists, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ReservedUsernameError, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(TracimValidationFailed, HTTPStatus.BAD_REQUEST) @check_right(is_administrator) @hapic.input_body(UserCreationSchema()) @hapic.output_body(UserSchema()) def create_user(self, context, request: TracimRequest, hapic_data=None): """ Create new user. Note: One of username or email required. """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) if hapic_data.body.profile: profile = Profile.get_profile_from_slug(hapic_data.body.profile) else: profile = None password = hapic_data.body.password if not password and hapic_data.body.email_notification: password = password_generator() user = uapi.create_user( auth_type=AuthType.UNKNOWN, email=hapic_data.body.email, password=password, timezone=hapic_data.body.timezone, lang=hapic_data.body.lang, name=hapic_data.body.public_name, username=hapic_data.body.username, do_notify=hapic_data.body.email_notification, allowed_space=hapic_data.body.allowed_space, profile=profile, do_save=True, ) uapi.execute_created_user_actions(user) return uapi.get_user_with_context(user) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENABLE_AND_DISABLE_ENDPOINTS]) @check_right(is_administrator) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def enable_user(self, context, request: TracimRequest, hapic_data=None): """ enable user """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) uapi.enable(user=request.candidate_user, do_save=True) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_TRASH_AND_RESTORE_ENDPOINTS]) @hapic.handle_exception(UserCantDeleteHimself, HTTPStatus.BAD_REQUEST) @check_right(is_administrator) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def delete_user(self, context, request: TracimRequest, hapic_data=None): """ delete user """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) uapi.delete(user=request.candidate_user, do_save=True) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_TRASH_AND_RESTORE_ENDPOINTS]) @check_right(is_administrator) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def undelete_user(self, context, request: TracimRequest, hapic_data=None): """ undelete user """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, show_deleted=True, ) uapi.undelete(user=request.candidate_user, do_save=True) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENABLE_AND_DISABLE_ENDPOINTS]) @hapic.handle_exception(UserCantDisableHimself, HTTPStatus.BAD_REQUEST) @check_right(is_administrator) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def disable_user(self, context, request: TracimRequest, hapic_data=None): """ disable user """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) uapi.disable(user=request.candidate_user, do_save=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @hapic.handle_exception(UserCantChangeIsOwnProfile, HTTPStatus.BAD_REQUEST) @check_right(is_administrator) @hapic.input_path(UserIdPathSchema()) @hapic.input_body(SetUserProfileSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_profile(self, context, request: TracimRequest, hapic_data=None): """ set user profile """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) profile = Profile.get_profile_from_slug(hapic_data.body.profile) uapi.update( user=request.candidate_user, auth_type=request.candidate_user.auth_type, profile=profile, do_save=True, ) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS]) @check_right(is_administrator) @hapic.input_path(UserIdPathSchema()) @hapic.input_body(SetUserAllowedSpaceSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_allowed_space(self, context, request: TracimRequest, hapic_data=None): """ set user allowed_space """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) uapi.update( user=request.candidate_user, auth_type=request.candidate_user.auth_type, allowed_space=hapic_data.body.allowed_space, do_save=True, ) return @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceIdPathSchema()) @hapic.input_query(ActiveContentFilterQuerySchema()) @hapic.output_body(ContentDigestSchema(many=True)) def last_active_content(self, context, request: TracimRequest, hapic_data=None): """ Get last_active_content for user """ app_config = request.registry.settings["CFG"] # type: CFG content_filter = hapic_data.query api = ContentApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = None if hapic_data.path.workspace_id: workspace = wapi.get_one(hapic_data.path.workspace_id) before_content = None if content_filter.before_content_id: before_content = api.get_one( content_id=content_filter.before_content_id, workspace=workspace, content_type=content_type_list.Any_SLUG, ) last_actives = api.get_last_active( workspace=workspace, limit=content_filter.limit or None, before_content=before_content ) return [api.get_content_in_context(content) for content in last_actives] @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceIdPathSchema()) @hapic.input_query(ContentIdsQuerySchema()) @hapic.output_body(ReadStatusSchema(many=True)) def contents_read_status(self, context, request: TracimRequest, hapic_data=None): """ get user_read status of contents """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = None if hapic_data.path.workspace_id: workspace = wapi.get_one(hapic_data.path.workspace_id) last_actives = api.get_last_active( workspace=workspace, limit=None, before_content=None, content_ids=hapic_data.query.content_ids or None, ) return [api.get_content_in_context(content) for content in last_actives] @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_content_as_read(self, context, request: TracimRequest, hapic_data=None): """ set user_read status of content to read """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.candidate_user, session=request.dbsession, config=app_config, ) api.mark_read(request.current_content, do_flush=True) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_content_as_unread(self, context, request: TracimRequest, hapic_data=None): """ set user_read status of content to unread """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.candidate_user, session=request.dbsession, config=app_config, ) api.mark_unread(request.current_content, do_flush=True) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONTENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_workspace_as_read(self, context, request: TracimRequest, hapic_data=None): """ set user_read status of all content of workspace to read """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.candidate_user, session=request.dbsession, config=app_config, ) api.mark_read__workspace(request.current_workspace) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_NOTIFICATION_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def enable_workspace_notification(self, context, request: TracimRequest, hapic_data=None): """ enable workspace notification """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = wapi.get_one(hapic_data.path.workspace_id) wapi.enable_notifications(request.candidate_user, workspace) wapi.save(workspace) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_NOTIFICATION_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserWorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def disable_workspace_notification(self, context, request: TracimRequest, hapic_data=None): """ disable workspace notification """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.candidate_user, # User session=request.dbsession, config=app_config, ) workspace = wapi.get_one(hapic_data.path.workspace_id) wapi.disable_notifications(request.candidate_user, workspace) wapi.save(workspace) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_EVENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.input_query(GetLiveMessageQuerySchema()) @hapic.output_body(LiveMessageSchemaPage()) def get_user_messages( self, context, request: TracimRequest, hapic_data: HapicData ) -> PaginatedObject: """ Returns user messages matching the given query """ app_config = request.registry.settings["CFG"] # type: CFG event_api = EventApi(request.current_user, request.dbsession, app_config) return PaginatedObject( event_api.get_paginated_messages_for_user( user_id=request.candidate_user.user_id, read_status=hapic_data.query.read_status, page_token=hapic_data.query.page_token, count=hapic_data.query.count, exclude_author_ids=hapic_data.query.exclude_author_ids, include_event_types=hapic_data.query.include_event_types, exclude_event_types=hapic_data.query.exclude_event_types, workspace_ids=hapic_data.query.workspace_ids, include_not_sent=hapic_data.query.include_not_sent, related_to_content_ids=hapic_data.query.related_to_content_ids, ) ) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_EVENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.input_query(UserMessagesSummaryQuerySchema()) @hapic.output_body(UserMessagesSummarySchema()) def get_user_messages_summary( self, context, request: TracimRequest, hapic_data: HapicData ) -> UserMessagesSummary: """ Returns a summary about messages filtered """ app_config = request.registry.settings["CFG"] # type: CFG event_api = EventApi(request.current_user, request.dbsession, app_config) candidate_user = UserApi( request.current_user, request.dbsession, app_config ).get_user_with_context(request.candidate_user) unread_messages_count = event_api.get_messages_count( user_id=candidate_user.user_id, read_status=ReadStatus.UNREAD, include_event_types=hapic_data.query.include_event_types, exclude_event_types=hapic_data.query.exclude_event_types, exclude_author_ids=hapic_data.query.exclude_author_ids, include_not_sent=hapic_data.query.include_not_sent, workspace_ids=hapic_data.query.workspace_ids, related_to_content_ids=hapic_data.query.related_to_content_ids, ) read_messages_count = event_api.get_messages_count( user_id=candidate_user.user_id, read_status=ReadStatus.READ, include_event_types=hapic_data.query.include_event_types, exclude_event_types=hapic_data.query.exclude_event_types, exclude_author_ids=hapic_data.query.exclude_author_ids, include_not_sent=hapic_data.query.include_not_sent, workspace_ids=hapic_data.query.workspace_ids, related_to_content_ids=hapic_data.query.related_to_content_ids, ) return UserMessagesSummary( user=candidate_user, unread_messages_count=unread_messages_count, read_messages_count=read_messages_count, ) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_EVENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_all_user_messages_as_read( self, context, request: TracimRequest, hapic_data: HapicData ) -> None: """ Read all unread message for user """ app_config = request.registry.settings["CFG"] # type: CFG event_api = EventApi(request.current_user, request.dbsession, app_config) event_api.mark_user_messages_as_read(request.candidate_user.user_id) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_EVENT_ENDPOINTS]) @hapic.handle_exception(MessageDoesNotExist, http_code=HTTPStatus.BAD_REQUEST) @check_right(has_personal_access) @hapic.input_path(MessageIdsPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_message_as_read(self, context, request: TracimRequest, hapic_data: HapicData) -> None: """ Read one message """ app_config = request.registry.settings["CFG"] # type: CFG event_api = EventApi(request.current_user, request.dbsession, app_config) event_api.mark_user_message_as_read( event_id=hapic_data.path.event_id, user_id=request.candidate_user.user_id ) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_EVENT_ENDPOINTS]) @hapic.handle_exception(MessageDoesNotExist, http_code=HTTPStatus.BAD_REQUEST) @check_right(has_personal_access) @hapic.input_path(MessageIdsPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_message_as_unread(self, context, request: TracimRequest, hapic_data: HapicData) -> None: """ unread one message """ app_config = request.registry.settings["CFG"] # type: CFG event_api = EventApi(request.current_user, request.dbsession, app_config) event_api.mark_user_message_as_unread( event_id=hapic_data.path.event_id, user_id=request.candidate_user.user_id ) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_EVENT_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.input_headers(TracimLiveEventHeaderSchema()) @hapic.input_query(TracimLiveEventQuerySchema()) def open_message_stream(self, context, request: TracimRequest, hapic_data) -> Response: """ Open the message stream for the given user. Tracim Live Message Events as ServerSide Event Stream """ stream_opened_event = ":Tracim Live Messages for user {}\n\nevent: stream-open\ndata:\n\n".format( str(request.candidate_user.user_id) ) after_event_id = hapic_data.query["after_event_id"] # type: int if after_event_id: app_config = request.registry.settings["CFG"] # type: CFG event_api = EventApi(request.current_user, request.dbsession, app_config) messages = event_api.get_messages_for_user( request.candidate_user.user_id, after_event_id=after_event_id ) # type: typing.List[Message] stream_opened_event += "".join( [ "data:" + json.dumps(LiveMessagesLib.message_as_dict(message)) + "\n\n" for message in messages ] ) escaped_keepalive_event = "event: keep-alive\\ndata:\\n\\n" user_channel_name = LiveMessagesLib.user_grip_channel(request.candidate_user.user_id) headers = [ # Here we ask push pin to keep the connection open ("Grip-Hold", "stream"), # and register this connection on the given channel # multiple channels subscription is possible ("Grip-Channel", user_channel_name), ("Grip-Keep-Alive", "{}; format=cstring; timeout=30".format(escaped_keepalive_event)), # content type for SSE ("Content-Type", "text/event-stream"), # do not cache the events ("Cache-Control", "no-cache"), ] return Response( headerlist=headers, charset="utf-8", status_code=200, body=stream_opened_event ) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONFIG_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(UserConfigSchema()) def get_user_config( self, context, request: TracimRequest, hapic_data: HapicData ) -> typing.Dict: """ get all the configuration parameters for the given user """ config_api = UserConfigApi(current_user=request.candidate_user, session=request.dbsession) return {"parameters": config_api.get_all_params()} @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONFIG_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.input_body(SetConfigSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def set_user_config(self, context, request: TracimRequest, hapic_data: HapicData) -> None: """ Set or update the given configuration parameters for the given user The behavior of this endpoint is adding/updating key (patch-like) but not replacing the whole configuration, so it's not possible to remove keys through this endpoint. """ config_api = UserConfigApi(current_user=request.candidate_user, session=request.dbsession) config_api.set_params(params=hapic_data.body["parameters"]) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_CONFIG_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(WorkspaceSchema(many=True)) def get_accessible_workspaces( self, context, request: TracimRequest, hapic_data: HapicData ) -> typing.List[WorkspaceInContext]: """ Return the list of accessible workspaces by the given user id. An accessible workspace is: - a workspace the user is not member of (`workspaces` API returns them) - has an OPEN or ON_REQUEST access type """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.candidate_user, session=request.dbsession, config=app_config, ) workspaces = wapi.get_all_accessible_by_user(request.candidate_user) return [wapi.get_workspace_with_context(workspace) for workspace in workspaces] @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_SUBSCRIPTIONS_ENDPOINTS]) @check_right(has_personal_access) @hapic.input_path(UserIdPathSchema()) @hapic.output_body(WorkspaceSubscriptionSchema(many=True)) def user_subscriptions( self, context, request: TracimRequest, hapic_data: HapicData ) -> typing.List[WorkspaceSubscription]: subscription_lib = SubscriptionLib( current_user=request.current_user, session=request.dbsession, config=request.app_config, ) return subscription_lib.get_user_subscription(request.candidate_user.user_id) @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_SUBSCRIPTIONS_ENDPOINTS]) @check_right(has_personal_access) @hapic.handle_exception(InvalidWorkspaceAccessType, HTTPStatus.BAD_REQUEST) @hapic.input_path(UserIdPathSchema()) @hapic.input_body(WorkspaceIdSchema()) @hapic.output_body(WorkspaceSubscriptionSchema()) def submit_subscription( self, context, request: TracimRequest, hapic_data: HapicData ) -> typing.List[WorkspaceSubscription]: workspace = WorkspaceApi( current_user=None, session=request.dbsession, config=request.app_config ).get_one(hapic_data.body["workspace_id"]) subscription_lib = SubscriptionLib( current_user=request.current_user, session=request.dbsession, config=request.app_config, ) return subscription_lib.submit_subscription(workspace=workspace) def bind(self, configurator: Configurator) -> None: """ Create all routes and views using pyramid configurator for this controller """ # user workspace configurator.add_route( "get_user_workspace", "/users/{user_id:\d+}/workspaces", request_method="GET", # noqa: W605 ) configurator.add_view(self.user_workspace, route_name="get_user_workspace") configurator.add_route( "post_user_workspace", "/users/{user_id:\d+}/workspaces", request_method="POST", # noqa: W605 ) configurator.add_view(self.join_workspace, route_name="post_user_workspace") # user info configurator.add_route("user", "/users/{user_id:\d+}", request_method="GET") # noqa: W605 configurator.add_view(self.user, route_name="user") # user space info configurator.add_route( "user_disk_space", "/users/{user_id:\d+}/disk_space", request_method="GET" ) # noqa: W605 configurator.add_view(self.user_disk_space, route_name="user_disk_space") # users lists configurator.add_route("users", "/users", request_method="GET") configurator.add_view(self.users, route_name="users") # known members lists configurator.add_route( "known_members", "/users/{user_id:\d+}/known_members", request_method="GET", # noqa: W605 ) configurator.add_view(self.known_members, route_name="known_members") # set user email configurator.add_route( "set_user_email", "/users/{user_id:\d+}/email", request_method="PUT" ) # noqa: W605 configurator.add_view(self.set_user_email, route_name="set_user_email") # set user username configurator.add_route( "set_user_username", "/users/{user_id:\d+}/username", request_method="PUT" ) # noqa: W605 configurator.add_view(self.set_user_username, route_name="set_user_username") # set user password configurator.add_route( "set_user_password", "/users/{user_id:\d+}/password", request_method="PUT" # noqa: W605 ) configurator.add_view(self.set_user_password, route_name="set_user_password") # set user_info configurator.add_route( "set_user_info", "/users/{user_id:\d+}", request_method="PUT" ) # noqa: W605 configurator.add_view(self.set_user_infos, route_name="set_user_info") # create user configurator.add_route("create_user", "/users", request_method="POST") configurator.add_view(self.create_user, route_name="create_user") # enable user configurator.add_route( "enable_user", "/users/{user_id:\d+}/enabled", request_method="PUT" ) # noqa: W605 configurator.add_view(self.enable_user, route_name="enable_user") # disable user configurator.add_route( "disable_user", "/users/{user_id:\d+}/disabled", request_method="PUT" # noqa: W605 ) configurator.add_view(self.disable_user, route_name="disable_user") # delete user configurator.add_route( "delete_user", "/users/{user_id:\d+}/trashed", request_method="PUT" ) # noqa: W605 configurator.add_view(self.delete_user, route_name="delete_user") # undelete user configurator.add_route( "undelete_user", "/users/{user_id:\d+}/trashed/restore", request_method="PUT", # noqa: W605 ) configurator.add_view(self.undelete_user, route_name="undelete_user") # set user profile configurator.add_route( "set_user_profile", "/users/{user_id:\d+}/profile", request_method="PUT" # noqa: W605 ) configurator.add_view(self.set_profile, route_name="set_user_profile") # set user allowed_space configurator.add_route( "set_user_allowed_space", "/users/{user_id:\d+}/allowed_space", request_method="PUT", # noqa: W605 ) configurator.add_view(self.set_allowed_space, route_name="set_user_allowed_space") # user content configurator.add_route( "contents_read_status", "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/read_status", # noqa: W605 request_method="GET", ) configurator.add_view(self.contents_read_status, route_name="contents_read_status") # last active content for user configurator.add_route( "last_active_content", "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/recently_active", # noqa: W605 request_method="GET", ) configurator.add_view(self.last_active_content, route_name="last_active_content") # set content as read/unread configurator.add_route( "read_content", "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/{content_id}/read", # noqa: W605 request_method="PUT", ) configurator.add_view(self.set_content_as_read, route_name="read_content") configurator.add_route( "unread_content", "/users/{user_id:\d+}/workspaces/{workspace_id}/contents/{content_id}/unread", # noqa: W605 request_method="PUT", ) configurator.add_view(self.set_content_as_unread, route_name="unread_content") # set workspace as read configurator.add_route( "read_workspace", "/users/{user_id:\d+}/workspaces/{workspace_id}/read", # noqa: W605 request_method="PUT", ) configurator.add_view(self.set_workspace_as_read, route_name="read_workspace") # enable workspace notification configurator.add_route( "enable_workspace_notification", "/users/{user_id:\d+}/workspaces/{workspace_id}/notifications/activate", # noqa: W605 request_method="PUT", ) configurator.add_view( self.enable_workspace_notification, route_name="enable_workspace_notification" ) # enable workspace notification configurator.add_route( "disable_workspace_notification", "/users/{user_id:\d+}/workspaces/{workspace_id}/notifications/deactivate", # noqa: W605 request_method="PUT", ) configurator.add_view( self.disable_workspace_notification, route_name="disable_workspace_notification" ) # TracimLiveMessages notification configurator.add_route( "live_messages", "/users/{user_id:\d+}/live_messages", # noqa: W605 request_method="GET", ) configurator.add_view(self.open_message_stream, route_name="live_messages") # Tracim user messages configurator.add_route( "messages", "/users/{user_id:\d+}/messages", request_method="GET", # noqa: W605 ) configurator.add_view(self.get_user_messages, route_name="messages") configurator.add_route( "messages_summary", "/users/{user_id:\d+}/messages/summary", request_method="GET", # noqa: W605 ) configurator.add_view(self.get_user_messages_summary, route_name="messages_summary") # read all unread messages for user configurator.add_route( "read_messages", "/users/{user_id:\d+}/messages/read", request_method="PUT", # noqa: W605 ) configurator.add_view(self.set_all_user_messages_as_read, route_name="read_messages") # read all unread messages for user configurator.add_route( "read_message", "/users/{user_id:\d+}/messages/{event_id:\d+}/read", request_method="PUT", # noqa: W605 ) configurator.add_view(self.set_message_as_read, route_name="read_message") # read all unread messages for user configurator.add_route( "unread_message", "/users/{user_id:\d+}/messages/{event_id:\d+}/unread", request_method="PUT", # noqa: W605 ) configurator.add_view(self.set_message_as_unread, route_name="unread_message") # User configuration configurator.add_route( "config_get", "/users/{user_id:\d+}/config", request_method="GET", # noqa: W605 ) configurator.add_view(self.get_user_config, route_name="config_get") configurator.add_route( "config_post", "/users/{user_id:\d+}/config", request_method="PUT", # noqa: W605 ) configurator.add_view(self.set_user_config, route_name="config_post") # User accessible workspaces (not member of, but can see information about them to subscribe) configurator.add_route( "get_accessible_workspaces", "/users/{user_id:\d+}/accessible_workspaces", request_method="GET", # noqa: W605 ) configurator.add_view( self.get_accessible_workspaces, route_name="get_accessible_workspaces" ) # User subscriptions configurator.add_route( "subscriptions_get", "/users/{user_id:\d+}/workspace_subscriptions", request_method="GET", # noqa: W605 ) configurator.add_view(self.user_subscriptions, route_name="subscriptions_get") configurator.add_route( "subscriptions_put", "/users/{user_id:\d+}/workspace_subscriptions", request_method="PUT", # noqa: W605 ) configurator.add_view(self.submit_subscription, route_name="subscriptions_put")
class ResetPasswordController(Controller): @hapic.with_api_doc( tags=[SWAGGER_TAG__AUTHENTICATION_RESET_PASSWORD_ENDPOINTS]) # nopep8 @hapic.handle_exception(NotificationDisabledCantResetPassword, http_code=HTTPStatus.BAD_REQUEST) # nopep8 @hapic.handle_exception(ExternalAuthUserPasswordModificationDisallowed, http_code=HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UserAuthTypeDisabled, http_code=HTTPStatus.BAD_REQUEST) @hapic.input_body(ResetPasswordRequestSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def reset_password_request(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Send a request to reset password. This will result in a new email sent to the user with a token to be used for password reset operation. """ app_config = request.registry.settings['CFG'] uapi = UserApi( None, session=request.dbsession, config=app_config, ) user = uapi.get_one_by_email(hapic_data.body.email) uapi.reset_password_notification(user, do_save=True) return @hapic.with_api_doc( tags=[SWAGGER_TAG__AUTHENTICATION_RESET_PASSWORD_ENDPOINTS]) # nopep8 @hapic.handle_exception(ExpiredResetPasswordToken, http_code=HTTPStatus.BAD_REQUEST) # nopep8 @hapic.handle_exception(UnvalidResetPasswordToken, http_code=HTTPStatus.BAD_REQUEST) # nopep8 @hapic.handle_exception(ExternalAuthUserPasswordModificationDisallowed, http_code=HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UserAuthTypeDisabled, http_code=HTTPStatus.BAD_REQUEST) @hapic.input_body(ResetPasswordCheckTokenSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def reset_password_check_token(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Check reset_password token. The token sent by email has a limited life duration, this API allow to check that the token is existing and still valid. """ app_config = request.registry.settings['CFG'] uapi = UserApi( None, session=request.dbsession, config=app_config, ) user = uapi.get_one_by_email(hapic_data.body.email) uapi.validate_reset_password_token( user, hapic_data.body.reset_password_token) # nopep8 return @hapic.with_api_doc( tags=[SWAGGER_TAG__AUTHENTICATION_RESET_PASSWORD_ENDPOINTS]) # nopep8 @hapic.handle_exception(ExpiredResetPasswordToken, http_code=HTTPStatus.BAD_REQUEST) # nopep8 @hapic.handle_exception(UnvalidResetPasswordToken, http_code=HTTPStatus.BAD_REQUEST) # nopep8 @hapic.handle_exception(ExternalAuthUserPasswordModificationDisallowed, http_code=HTTPStatus.BAD_REQUEST) @hapic.handle_exception(PasswordDoNotMatch, http_code=HTTPStatus.BAD_REQUEST) # nopep8 @hapic.handle_exception(UserAuthTypeDisabled, http_code=HTTPStatus.BAD_REQUEST) @hapic.input_body(ResetPasswordModifySchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def reset_password_modify(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Do change the password. This requires the token received by email. After this request returns a 200, the user password is effectively changed """ app_config = request.registry.settings['CFG'] uapi = UserApi( None, session=request.dbsession, config=app_config, ) user = uapi.get_one_by_email(hapic_data.body.email) uapi.set_password_reset_token( new_password=hapic_data.body.new_password, new_password2=hapic_data.body.new_password2, reset_token=hapic_data.body.reset_password_token, user=user, do_save=True) return def bind(self, configurator: Configurator): # reset password request configurator.add_route('reset_password_request', '/auth/password/reset/request', request_method='POST') # nopep8 configurator.add_view(self.reset_password_request, route_name='reset_password_request') # nopep8 # check reset password token configurator.add_route('reset_password_check_token', '/auth/password/reset/token/check', request_method='POST') # nopep8 configurator.add_view( self.reset_password_check_token, route_name='reset_password_check_token') # nopep8 # reset password, set password configurator.add_route('reset_password_modify', '/auth/password/reset/modify', request_method='POST') # nopep8 configurator.add_view(self.reset_password_modify, route_name='reset_password_modify') # nopep8
class AccountController(Controller): @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_ENDPOINTS]) @check_right(is_user) @hapic.output_body(UserSchema()) def account(self, context, request: TracimRequest, hapic_data=None): """ Get user infos. """ app_config = request.registry.settings['CFG'] uapi = UserApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) return uapi.get_user_with_context(request.current_user) @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_ENDPOINTS]) @check_right(is_user) @hapic.input_body(SetUserInfoSchema()) @hapic.output_body(UserSchema()) def set_account_infos(self, context, request: TracimRequest, hapic_data=None): """ Set user info data """ app_config = request.registry.settings['CFG'] uapi = UserApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) user = uapi.update(request.current_user, name=hapic_data.body.public_name, timezone=hapic_data.body.timezone, lang=hapic_data.body.lang, do_save=True) return uapi.get_user_with_context(user) @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_ENDPOINTS]) @check_right(is_user) @hapic.input_query(KnownMemberQuerySchema()) @hapic.output_body(UserDigestSchema(many=True)) def account_known_members(self, context, request: TracimRequest, hapic_data=None): """ Get known users list """ app_config = request.registry.settings['CFG'] uapi = UserApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, show_deactivated=False, ) users = uapi.get_known_user( acp=hapic_data.query.acp, exclude_user_ids=hapic_data.query.exclude_user_ids, exclude_workspace_ids=hapic_data.query.exclude_workspace_ids, ) context_users = [uapi.get_user_with_context(user) for user in users] return context_users @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_ENDPOINTS]) @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN) @hapic.handle_exception(EmailAlreadyExistInDb, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ExternalAuthUserEmailModificationDisallowed, HTTPStatus.BAD_REQUEST) # nopep8 @check_right(is_user) @hapic.input_body(SetEmailSchema()) @hapic.output_body(UserSchema()) def set_account_email(self, context, request: TracimRequest, hapic_data=None): """ Set user Email """ app_config = request.registry.settings['CFG'] uapi = UserApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) user = uapi.set_email(request.current_user, hapic_data.body.loggedin_user_password, hapic_data.body.email, do_save=True) return uapi.get_user_with_context(user) @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_ENDPOINTS]) @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN) @hapic.handle_exception(PasswordDoNotMatch, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ExternalAuthUserPasswordModificationDisallowed, HTTPStatus.BAD_REQUEST) # nopep8 @check_right(is_user) @hapic.input_body(SetPasswordSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def set_account_password(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Set user password """ app_config = request.registry.settings['CFG'] uapi = UserApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) uapi.set_password(request.current_user, hapic_data.body.loggedin_user_password, hapic_data.body.new_password, hapic_data.body.new_password2, do_save=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_CONTENT_ENDPOINTS]) @check_right(is_user) @hapic.output_body( WorkspaceDigestSchema(many=True), ) def account_workspace(self, context, request: TracimRequest, hapic_data=None): """ Get list of auth user workspaces """ app_config = request.registry.settings['CFG'] wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) workspaces = wapi.get_all_for_user(request.current_user) return [ wapi.get_workspace_with_context(workspace) for workspace in workspaces ] @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_CONTENT_ENDPOINTS]) @check_right(is_user) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.input_query(ActiveContentFilterQuerySchema()) @hapic.output_body(ContentDigestSchema(many=True)) def account_last_active_content(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ Get last_active_content for user """ app_config = request.registry.settings['CFG'] content_filter = hapic_data.query api = ContentApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) workspace = None if hapic_data.path.workspace_id: workspace = wapi.get_one(hapic_data.path.workspace_id) before_content = None if content_filter.before_content_id: before_content = api.get_one( content_id=content_filter.before_content_id, workspace=workspace, content_type=content_type_list.Any_SLUG) last_actives = api.get_last_active( workspace=workspace, limit=content_filter.limit or None, before_content=before_content, ) return [ api.get_content_in_context(content) for content in last_actives ] @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_CONTENT_ENDPOINTS]) @check_right(is_user) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.input_query(ContentIdsQuerySchema()) @hapic.output_body(ReadStatusSchema(many=True)) # nopep8 def account_contents_read_status(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ get user_read status of contents """ app_config = request.registry.settings['CFG'] content_filter = hapic_data.query api = ContentApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) workspace = None if hapic_data.path.workspace_id: workspace = wapi.get_one(hapic_data.path.workspace_id) last_actives = api.get_last_active( workspace=workspace, limit=None, before_content=None, content_ids=hapic_data.query.content_ids or None) return [ api.get_content_in_context(content) for content in last_actives ] @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_CONTENT_ENDPOINTS]) @check_right(is_user) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def set_account_content_as_read(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ set user_read status of content to read """ app_config = request.registry.settings['CFG'] api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) api.mark_read(request.current_content, do_flush=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_CONTENT_ENDPOINTS]) @check_right(is_user) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def set_account_content_as_unread(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ set user_read status of content to unread """ app_config = request.registry.settings['CFG'] api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) api.mark_unread(request.current_content, do_flush=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_CONTENT_ENDPOINTS]) @check_right(is_user) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def set_account_workspace_as_read(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ set user_read status of all content of workspace to read """ app_config = request.registry.settings['CFG'] api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) api.mark_read__workspace(request.current_workspace) return @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_NOTIFICATION_ENDPOINTS]) @check_right(is_user) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def enable_account_workspace_notification(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ enable workspace notification """ app_config = request.registry.settings['CFG'] api = ContentApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) workspace = wapi.get_one(hapic_data.path.workspace_id) wapi.enable_notifications(request.current_user, workspace) rapi = RoleApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) role = rapi.get_one(request.current_user.user_id, workspace.workspace_id) # nopep8 wapi.save(workspace) return @hapic.with_api_doc(tags=[SWAGGER_TAG__ACCOUNT_NOTIFICATION_ENDPOINTS]) @check_right(is_user) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def disable_account_workspace_notification(self, context, request: TracimRequest, hapic_data=None): # nopep8 """ disable workspace notification """ app_config = request.registry.settings['CFG'] api = ContentApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, ) workspace = wapi.get_one(hapic_data.path.workspace_id) wapi.disable_notifications(request.current_user, workspace) wapi.save(workspace) return def bind(self, configurator: Configurator) -> None: """ Create all routes and views using pyramid configurator for this controller """ # account workspace configurator.add_route('account_workspace', '/users/me/workspaces', request_method='GET') # nopep8 configurator.add_view(self.account_workspace, route_name='account_workspace') # account info configurator.add_route('account', '/users/me', request_method='GET') # nopep8 configurator.add_view(self.account, route_name='account') # # # known members lists configurator.add_route('account_known_members', '/users/me/known_members', request_method='GET') # nopep8 configurator.add_view(self.account_known_members, route_name='account_known_members') # # set account email configurator.add_route('set_account_email', '/users/me/email', request_method='PUT') # nopep8 configurator.add_view(self.set_account_email, route_name='set_account_email') # set account password configurator.add_route('set_account_password', '/users/me/password', request_method='PUT') # nopep8 configurator.add_view(self.set_account_password, route_name='set_account_password') # nopep8 # set account_infos configurator.add_route('set_account_info', '/users/me', request_method='PUT') # nopep8 configurator.add_view(self.set_account_infos, route_name='set_account_info') # account content configurator.add_route( 'account_contents_read_status', '/users/me/workspaces/{workspace_id}/contents/read_status', request_method='GET') # nopep8 configurator.add_view( self.account_contents_read_status, route_name='account_contents_read_status') # nopep8 # last active content for user configurator.add_route( 'account_last_active_content', '/users/me/workspaces/{workspace_id}/contents/recently_active', request_method='GET') # nopep8 configurator.add_view( self.account_last_active_content, route_name='account_last_active_content') # nopep8 # set content as read/unread configurator.add_route( 'account_read_content', '/users/me/workspaces/{workspace_id}/contents/{content_id}/read', request_method='PUT') # nopep8 configurator.add_view(self.set_account_content_as_read, route_name='account_read_content') # nopep8 configurator.add_route( 'account_unread_content', '/users/me/workspaces/{workspace_id}/contents/{content_id}/unread', request_method='PUT') # nopep8 configurator.add_view(self.set_account_content_as_unread, route_name='account_unread_content') # nopep8 # set workspace as read configurator.add_route('account_read_workspace', '/users/me/workspaces/{workspace_id}/read', request_method='PUT') # nopep8 configurator.add_view(self.set_account_workspace_as_read, route_name='account_read_workspace') # nopep8 # enable workspace notification configurator.add_route( 'enable_account_workspace_notification', '/users/me/workspaces/{workspace_id}/notifications/activate', request_method='PUT') # nopep8 configurator.add_view( self.enable_account_workspace_notification, route_name='enable_account_workspace_notification') # nopep8 # disable workspace notification configurator.add_route( 'disable_account_workspace_notification', '/users/me/workspaces/{workspace_id}/notifications/deactivate', request_method='PUT') # nopep8 configurator.add_view( self.disable_account_workspace_notification, route_name='disable_account_workspace_notification') # nopep8
class SessionController(Controller): @hapic.with_api_doc(tags=[SWAGGER_TAG__AUTHENTICATION_ENDPOINTS]) @hapic.input_headers(LoginOutputHeaders()) @hapic.input_body(BasicAuthSchema()) @hapic.output_body(UserSchema()) def login(self, context, request: TracimRequest, hapic_data: HapicData) -> UserInContext: """ Logs the user into the system. In case of success, the JSON returned is the user profile. In that case, a cookie is created with a session_key and an expiration date. Eg. : `session_key=932d2ad68f3a094c2d4da563ccb921e6479729f5b5f707eba91d4194979df20831be48a0; expires=Mon, 22-Oct-2018 19:37:02 GMT; Path=/; SameSite=Lax` """ login = hapic_data.body # type: LoginCredentials app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi(None, session=request.dbsession, config=app_config) ldap_connector = None if AuthType.LDAP in app_config.AUTH_TYPES: ldap_connector = get_ldap_connector(request) user = None if login.email: try: user = uapi.authenticate( login=login.email, password=login.password, ldap_connector=ldap_connector, ) except AuthenticationFailed as exc: if not login.username: raise exc if user is None: user = uapi.authenticate( login=login.username, password=login.password, ldap_connector=ldap_connector, ) remember(request, user.user_id) return uapi.get_user_with_context(user) @hapic.with_api_doc(tags=[SWAGGER_TAG__AUTHENTICATION_ENDPOINTS]) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def logout(self, context, request: TracimRequest, hapic_data=None): """ Logs out current logged in user. This also trashes the associated session and the live message connections of the current user. """ if request.authenticated_userid: app_config = request.registry.settings["CFG"] # type: CFG LiveMessagesLib(app_config).close_channel_connections( LiveMessagesLib.user_grip_channel( request.current_user.user_id)) forget(request) return @hapic.with_api_doc(tags=[SWAGGER_TAG__AUTHENTICATION_ENDPOINTS]) @hapic.output_body(UserSchema()) def whoami(self, context, request: TracimRequest, hapic_data=None): """ Return current logged-in user. If user is not authenticated or the session has expired, a 401 is returned. This is the recommanded way to check if the user is already authenticated """ app_config = request.registry.settings["CFG"] # type: CFG uapi = UserApi(request.current_user, session=request.dbsession, config=app_config) user = uapi.get_current_user() # User return uapi.get_user_with_context(user) def bind(self, configurator: Configurator): # Login configurator.add_route("login", "/auth/login", request_method="POST") configurator.add_view(self.login, route_name="login") # Logout configurator.add_route("logout", "/auth/logout", request_method="POST") configurator.add_view(self.logout, route_name="logout") configurator.add_route("logout_get", "/auth/logout", request_method="GET") configurator.add_view(self.logout, route_name="logout_get") # Whoami configurator.add_route("whoami", "/auth/whoami", request_method="GET") configurator.add_view(self.whoami, route_name="whoami")
class HTMLDocumentController(Controller): @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_HTML_DOCUMENT_ENDPOINTS]) @check_right(is_reader) @check_right(is_html_document_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(TextBasedContentSchema()) def get_html_document( self, context, request: TracimRequest, hapic_data=None ) -> ContentInContext: """ Get html document content """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) return api.get_content_in_context(content) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_HTML_DOCUMENT_ENDPOINTS]) @check_right(is_reader) @check_right(is_html_document_content) @hapic.input_query(FileQuerySchema()) @hapic.input_path(FilePathSchema()) @hapic.output_file([]) def get_html_document_preview( self, context, request: TracimRequest, hapic_data=None ) -> HapicFile: """ Download preview of html document Good pratice for filename is filename is `{label}{file_extension}` or `{filename}`. Default filename value is 'raw' (without file extension) or nothing. """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) file = BytesIO() byte_size = file.write(content.description.encode("utf-8")) file.seek(0) filename = hapic_data.path.filename # INFO - G.M - 2019-08-08 - use given filename in all case but none or # "raw", where filename returned will be original file one. if not filename or filename == "raw": filename = content.file_name return HapicFile( file_object=file, mimetype=CONTENT_TYPE_TEXT_HTML, filename=filename, as_attachment=hapic_data.query.force_download, content_length=byte_size, last_modified=content.updated, ) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_HTML_DOCUMENT_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UserNotMemberOfWorkspace, HTTPStatus.BAD_REQUEST) @check_right(is_contributor) @check_right(is_html_document_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.input_body(TextBasedContentModifySchema()) @hapic.output_body(TextBasedContentSchema()) def update_html_document( self, context, request: TracimRequest, hapic_data=None ) -> ContentInContext: """ update_html_document """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.update_content( item=content, new_label=hapic_data.body.label, new_content=hapic_data.body.raw_content, ) api.save(content) api.execute_update_content_actions(content) return api.get_content_in_context(content) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_HTML_DOCUMENT_ENDPOINTS]) @check_right(is_reader) @check_right(is_html_document_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(TextBasedRevisionSchema(many=True)) def get_html_document_revisions( self, context, request: TracimRequest, hapic_data=None ) -> typing.List[RevisionInContext]: """ get html_document revisions """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) revisions = content.revisions return [api.get_revision_in_context(revision) for revision in revisions] @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_HTML_DOCUMENT_ENDPOINTS]) @check_right(is_contributor) @check_right(is_html_document_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.input_body(SetContentStatusSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) @hapic.handle_exception(ContentStatusException, HTTPStatus.BAD_REQUEST) def set_html_document_status(self, context, request: TracimRequest, hapic_data=None) -> None: """ set html_document status """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(hapic_data.path.content_id, content_type=content_type_list.Any_SLUG) if content.status == request.json_body.get("status"): raise ContentStatusException( "Content id {} already have status {}".format(content.content_id, content.status) ) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.set_status(content, hapic_data.body.status) api.save(content) api.execute_update_content_actions(content) return def bind(self, configurator: Configurator) -> None: # Get html-document configurator.add_route( "html_document", "/workspaces/{workspace_id}/html-documents/{content_id}", request_method="GET", ) configurator.add_view(self.get_html_document, route_name="html_document") # get html-document preview configurator.add_route( "preview_html", "/workspaces/{workspace_id}/html-documents/{content_id}/preview/html/{filename:[^/]*}", request_method="GET", ) configurator.add_view(self.get_html_document_preview, route_name="preview_html") # update html-document configurator.add_route( "update_html_document", "/workspaces/{workspace_id}/html-documents/{content_id}", request_method="PUT", ) configurator.add_view(self.update_html_document, route_name="update_html_document") # get html document revisions configurator.add_route( "html_document_revisions", "/workspaces/{workspace_id}/html-documents/{content_id}/revisions", request_method="GET", ) configurator.add_view( self.get_html_document_revisions, route_name="html_document_revisions" ) # get html document revisions configurator.add_route( "set_html_document_status", "/workspaces/{workspace_id}/html-documents/{content_id}/status", request_method="PUT", ) configurator.add_view(self.set_html_document_status, route_name="set_html_document_status")
class SessionController(Controller): @hapic.with_api_doc(tags=[SWAGGER_TAG__AUTHENTICATION_ENDPOINTS]) @hapic.input_headers(LoginOutputHeaders()) @hapic.input_body(BasicAuthSchema()) @hapic.output_body(UserSchema()) def login(self, context, request: TracimRequest, hapic_data=None): """ Logs the user into the system. In case of success, the JSON returned is the user profile. In that case, a cookie is created with a session_key and an expiration date. Eg. : `session_key=932d2ad68f3a094c2d4da563ccb921e6479729f5b5f707eba91d4194979df20831be48a0; expires=Mon, 22-Oct-2018 19:37:02 GMT; Path=/; SameSite=Lax` """ login = hapic_data.body app_config = request.registry.settings['CFG'] uapi = UserApi( None, session=request.dbsession, config=app_config, ) user = uapi.authenticate_user(login.email, login.password) remember(request, user.user_id) return uapi.get_user_with_context(user) @hapic.with_api_doc(tags=[SWAGGER_TAG__AUTHENTICATION_ENDPOINTS]) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8 def logout(self, context, request: TracimRequest, hapic_data=None): """ Logs out current logged in user. This also trashes the associated session """ request.session.delete() return @hapic.with_api_doc(tags=[SWAGGER_TAG__AUTHENTICATION_ENDPOINTS]) @hapic.output_body( UserSchema(), ) def whoami(self, context, request: TracimRequest, hapic_data=None): """ Return current logged-in user. If user is not authenticated or the session has expired, a 401 is returned. This is the recommanded way to check if the user is already authenticated """ app_config = request.registry.settings['CFG'] uapi = UserApi( request.current_user, session=request.dbsession, config=app_config, ) user = uapi.get_current_user() # User return uapi.get_user_with_context(user) def bind(self, configurator: Configurator): # Login configurator.add_route('login', '/auth/login', request_method='POST') # nopep8 configurator.add_view(self.login, route_name='login') # Logout configurator.add_route('logout', '/auth/logout', request_method='POST') # nopep8 configurator.add_view(self.logout, route_name='logout') configurator.add_route('logout_get', '/auth/logout', request_method='GET') # nopep8 configurator.add_view(self.logout, route_name='logout_get') # Whoami configurator.add_route('whoami', '/auth/whoami', request_method='GET') # nopep8 configurator.add_view( self.whoami, route_name='whoami', )
class WorkspaceController(Controller): @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS]) @check_right(can_see_workspace_information) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(WorkspaceSchema()) def workspace(self, context, request: TracimRequest, hapic_data=None): """ Get workspace informations """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) return wapi.get_workspace_with_context(request.current_workspace) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS]) @check_right(can_see_workspace_information) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(WorkspaceDiskSpaceSchema()) def workspace_disk_space(self, context, request: TracimRequest, hapic_data=None): """ Get workspace space info """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) return wapi.get_workspace_with_context(request.current_workspace) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS]) @check_right(is_administrator) @hapic.input_query(WorkspaceFilterQuerySchema()) @hapic.output_body(WorkspaceSchema(many=True)) def workspaces(self, context, request: TracimRequest, hapic_data=None): """ Returns the list of all workspaces. This route is for admin only. Standard users must use their own route: /api/users/me/workspaces Filtering by parent_ids is possible through this endpoint """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) workspaces = wapi.get_all_children( parent_ids=hapic_data.query.parent_ids) return [ wapi.get_workspace_with_context(workspace) for workspace in workspaces ] @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(DisallowedWorkspaceAccessType, HTTPStatus.BAD_REQUEST) @check_right(can_modify_workspace) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.input_body(WorkspaceModifySchema()) @hapic.output_body(WorkspaceSchema()) def update_workspace(self, context, request: TracimRequest, hapic_data=None): """ Update a workspace. This route is for trusted users and administrators. Note : a trusted user can only update spaces on which he/she is space manager """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) wapi.update_workspace( request.current_workspace, label=hapic_data.body.label, description=hapic_data.body.description, agenda_enabled=hapic_data.body.agenda_enabled, public_download_enabled=hapic_data.body.public_download_enabled, public_upload_enabled=hapic_data.body.public_upload_enabled, default_user_role=hapic_data.body.default_user_role, save_now=True, ) wapi.execute_update_workspace_actions(request.current_workspace) return wapi.get_workspace_with_context(request.current_workspace) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(DisallowedWorkspaceAccessType, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UserNotAllowedToCreateMoreWorkspace, HTTPStatus.BAD_REQUEST) @check_right(is_trusted_user) @hapic.input_body(WorkspaceCreationSchema()) @hapic.output_body(WorkspaceSchema()) def create_workspace(self, context, request: TracimRequest, hapic_data=None): """ Create a workspace. This route is for trusted users and administrators. """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) parent = None if hapic_data.body.parent_id: parent = wapi.get_one(workspace_id=hapic_data.body.parent_id) workspace = wapi.create_workspace( label=hapic_data.body.label, description=hapic_data.body.description, access_type=hapic_data.body.access_type, save_now=True, agenda_enabled=hapic_data.body.agenda_enabled, public_download_enabled=hapic_data.body.public_download_enabled, public_upload_enabled=hapic_data.body.public_upload_enabled, default_user_role=hapic_data.body.default_user_role, parent=parent, ) wapi.execute_created_workspace_actions(workspace) return wapi.get_workspace_with_context(workspace) @hapic.with_api_doc( tags=[SWAGGER_TAG__WORKSPACE_TRASH_AND_RESTORE_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @check_right(can_delete_workspace) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def delete_workspace(self, context, request: TracimRequest, hapic_data=None): """ Delete a workspace. This route is for trusted users and administrators. Note : a trusted user can only delete spaces on which he/she is space manager """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.current_user, session=request.dbsession, config=app_config # User ) wapi.delete(request.current_workspace, flush=True) return @hapic.with_api_doc( tags=[SWAGGER_TAG__WORKSPACE_TRASH_AND_RESTORE_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @check_right(can_delete_workspace) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def undelete_workspace(self, context, request: TracimRequest, hapic_data=None): """ Restore a deleted space. Note : a trusted user can only restore spaces on which he/she is space manager """ app_config = request.registry.settings["CFG"] # type: CFG wapi = WorkspaceApi( current_user=request.current_user, # User session=request.dbsession, config=app_config, show_deleted=True, ) wapi.undelete(request.current_workspace, flush=True) return @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS]) @check_right(can_see_workspace_information) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.input_query(WorkspaceMemberFilterQuerySchema()) @hapic.output_body(WorkspaceMemberSchema(many=True)) def workspaces_members( self, context, request: TracimRequest, hapic_data=None) -> typing.List[UserRoleWorkspaceInContext]: """ Returns the list of space members with their role, avatar, etc. """ app_config = request.registry.settings["CFG"] # type: CFG rapi = RoleApi( current_user=request.current_user, session=request.dbsession, config=app_config, show_disabled_user=hapic_data.query.show_disabled_user, ) roles = rapi.get_all_for_workspace(workspace=request.current_workspace) return [ rapi.get_user_role_workspace_with_context(user_role) for user_role in roles ] @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS]) @check_right(can_see_workspace_information) @hapic.input_path(WorkspaceAndUserIdPathSchema()) @hapic.output_body(WorkspaceMemberSchema()) def workspaces_member_role(self, context, request: TracimRequest, hapic_data=None) -> UserRoleWorkspaceInContext: """ Returns given space member with its role, avatar, etc. """ app_config = request.registry.settings["CFG"] # type: CFG rapi = RoleApi(current_user=request.current_user, session=request.dbsession, config=app_config) role = rapi.get_one(user_id=hapic_data.path.user_id, workspace_id=hapic_data.path.workspace_id) return rapi.get_user_role_workspace_with_context(role) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS]) @hapic.handle_exception(UserRoleNotFound, HTTPStatus.BAD_REQUEST) @check_right(can_modify_workspace) @hapic.handle_exception(LastWorkspaceManagerRoleCantBeModified, HTTPStatus.BAD_REQUEST) @hapic.input_path(WorkspaceAndUserIdPathSchema()) @hapic.input_body(RoleUpdateSchema()) @hapic.output_body(WorkspaceMemberSchema()) def update_workspaces_members_role( self, context, request: TracimRequest, hapic_data=None) -> UserRoleWorkspaceInContext: """ Update role of the given space member. This feature is for workspace managers, trusted users and administrators. """ app_config = request.registry.settings["CFG"] # type: CFG rapi = RoleApi(current_user=request.current_user, session=request.dbsession, config=app_config) role = rapi.get_one(user_id=hapic_data.path.user_id, workspace_id=hapic_data.path.workspace_id) role = rapi.update_role(role, role_level=hapic_data.body.role.level) return rapi.get_user_role_workspace_with_context(role) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS]) @check_right(can_leave_workspace) @hapic.handle_exception(UserRoleNotFound, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(LastWorkspaceManagerRoleCantBeModified, HTTPStatus.BAD_REQUEST) @hapic.input_path(WorkspaceAndUserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def delete_workspaces_members_role(self, context, request: TracimRequest, hapic_data=None) -> None: """ Remove the user from the space. This feature is for workspace managers and administrators. """ app_config = request.registry.settings["CFG"] # type: CFG rapi = RoleApi(current_user=request.current_user, session=request.dbsession, config=app_config) rapi.delete_one(user_id=hapic_data.path.user_id, workspace_id=hapic_data.path.workspace_id) return @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_MEMBERS_ENDPOINTS]) @hapic.handle_exception(EmailValidationFailed, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UserIsNotActive, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UserIsDeleted, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(RoleAlreadyExistError, HTTPStatus.BAD_REQUEST) @check_right(can_modify_workspace) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.input_body(WorkspaceMemberInviteSchema()) @hapic.output_body(WorkspaceMemberCreationSchema()) def create_workspaces_members_role( self, context, request: TracimRequest, hapic_data=None) -> UserRoleWorkspaceInContext: """ Add a member to this workspace. This feature is for workspace managers and administrators. """ newly_created = False email_sent = False app_config = request.registry.settings["CFG"] # type: CFG rapi = RoleApi(current_user=request.current_user, session=request.dbsession, config=app_config) uapi = UserApi( current_user=request.current_user, session=request.dbsession, config=app_config, show_deactivated=True, show_deleted=True, ) try: if hapic_data.body.user_id: user = uapi.get_one(hapic_data.body.user_id) elif hapic_data.body.user_email: user = uapi.get_one_by_email(hapic_data.body.user_email) else: user = uapi.get_one_by_username(hapic_data.body.user_username) if user.is_deleted: raise UserIsDeleted( "This user has been deleted. Unable to invite him.") if not user.is_active: raise UserIsNotActive( "This user is not activated. Unable to invite him") except UserDoesNotExist as exc: if not uapi.allowed_to_invite_new_user(hapic_data.body.user_email): raise exc if app_config.NEW_USER__INVITATION__DO_NOTIFY: user = uapi.create_user( auth_type=AuthType.UNKNOWN, email=hapic_data.body.user_email, password=password_generator(), do_notify=True, ) if (app_config.EMAIL__NOTIFICATION__ACTIVATED and app_config.NEW_USER__INVITATION__DO_NOTIFY and app_config.JOBS__PROCESSING_MODE == app_config.CST.SYNC): email_sent = True else: user = uapi.create_user( auth_type=AuthType.UNKNOWN, email=hapic_data.body.user_email, password=None, do_notify=False, ) uapi.execute_created_user_actions(user) newly_created = True role = rapi.create_one( user=user, workspace=request.current_workspace, role_level=WorkspaceRoles.get_role_from_slug( hapic_data.body.role).level, with_notif=app_config.EMAIL__NOTIFICATION__ENABLED_ON_INVITATION, flush=True, ) return rapi.get_user_role_workspace_with_context( role, newly_created=newly_created, email_sent=email_sent) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_SUBSCRIPTION_ENDPOINTS]) @check_right(can_modify_workspace) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.output_body(WorkspaceSubscriptionSchema(many=True)) def workspace_subscriptions( self, context, request: TracimRequest, hapic_data=None) -> typing.List[WorkspaceSubscription]: subscription_lib = SubscriptionLib( current_user=request.current_user, session=request.dbsession, config=request.app_config, ) return subscription_lib.get_workspace_subscriptions( request.current_workspace.workspace_id) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_SUBSCRIPTION_ENDPOINTS]) @check_right(can_modify_workspace) @hapic.handle_exception(RoleAlreadyExistError, HTTPStatus.BAD_REQUEST) @hapic.input_path(WorkspaceAndUserIdPathSchema()) @hapic.input_body(RoleUpdateSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def accept_subscription( self, context, request: TracimRequest, hapic_data=None) -> typing.List[WorkspaceSubscription]: subscription_lib = SubscriptionLib( current_user=request.current_user, session=request.dbsession, config=request.app_config, ) subscription = subscription_lib.get_one( author_id=hapic_data.path.user_id, workspace_id=hapic_data.path.workspace_id) subscription_lib.accept_subscription(subscription=subscription, user_role=hapic_data.body.role) @hapic.with_api_doc(tags=[SWAGGER_TAG__WORKSPACE_SUBSCRIPTION_ENDPOINTS]) @check_right(can_modify_workspace) @hapic.input_path(WorkspaceAndUserIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def reject_subscription( self, context, request: TracimRequest, hapic_data=None) -> typing.List[WorkspaceSubscription]: subscription_lib = SubscriptionLib( current_user=request.current_user, session=request.dbsession, config=request.app_config, ) subscription = subscription_lib.get_one( author_id=hapic_data.path.user_id, workspace_id=hapic_data.path.workspace_id) subscription_lib.reject_subscription(subscription=subscription) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS]) @check_right(is_reader) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.input_query(FilterContentQuerySchema()) @hapic.output_body(ContentDigestSchema(many=True)) def workspace_content(self, context, request: TracimRequest, hapic_data=None) -> typing.List[ContentInContext]: """ return a list of contents of the space. This is NOT the full content list: by default, returned contents are the ones at root level. In order to get contents in a given folder, then use parent_id query filter. You can also show.hide archived/deleted contents. """ app_config = request.registry.settings["CFG"] # type: CFG content_filter = hapic_data.query api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config, namespaces_filter=content_filter.namespaces_filter or [ContentNamespaces.CONTENT], show_archived=content_filter.show_archived, show_deleted=content_filter.show_deleted, show_active=content_filter.show_active, ) contents = api.get_all( parent_ids=content_filter.parent_ids, complete_path_to_id=content_filter.complete_path_to_id, workspace=request.current_workspace, content_type=content_filter.content_type or content_type_list.Any_SLUG, label=content_filter.label, order_by_properties=[Content.label], ) contents = [ api.get_content_in_context(content) for content in contents ] return contents @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS]) @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UnallowedSubContent, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ParentNotFound, HTTPStatus.BAD_REQUEST) @check_right(can_create_content) @hapic.input_path(WorkspaceIdPathSchema()) @hapic.input_body(ContentCreationSchema()) @hapic.output_body(ContentDigestSchema()) def create_generic_empty_content(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: """ Creates a generic empty content. The minimum viable content has a label and a content type. Creating a content generally starts with a request to this endpoint. For specific contents like files, it is recommended to use the dedicated endpoint. This feature is accessible to contributors and higher role only. """ app_config = request.registry.settings["CFG"] # type: CFG creation_data = hapic_data.body api = ContentApi(current_user=request.current_user, session=request.dbsession, config=app_config) parent = None if creation_data.parent_id: try: parent = api.get_one(content_id=creation_data.parent_id, content_type=content_type_list.Any_SLUG) except ContentNotFound as exc: raise ParentNotFound( "Parent with content_id {} not found".format( creation_data.parent_id)) from exc content = api.create( label=creation_data.label, content_type_slug=creation_data.content_type, workspace=request.current_workspace, parent=parent, ) api.save(content, ActionDescription.CREATION) api.execute_created_content_actions(content) content = api.get_content_in_context(content) return content @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS]) @check_right(is_reader) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.FOUND) def get_content_from_workspace(self, context, request: TracimRequest, hapic_data=None) -> None: """ Convenient route allowing to get detail about a content without to known routes associated to its content type. This route generate a HTTP 302 with the right url """ content = request.current_content content_type = content_type_list.get_one_by_slug(content.type).slug # TODO - G.M - 2018-08-03 - Jsonify redirect response ? raise HTTPFound( "{base_url}workspaces/{workspace_id}/{content_type}s/{content_id}". format( base_url=BASE_API, workspace_id=content.workspace_id, content_type=content_type, content_id=content.content_id, )) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS]) @hapic.input_path(ContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.FOUND) def get_content(self, context, request: TracimRequest, hapic_data=None) -> None: """ Convenient route allowing to get detail about a content without to known routes associated to its content type. This route generate a HTTP 302 with the right url """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi(current_user=request.current_user, session=request.dbsession, config=app_config) content = api.get_one(content_id=hapic_data.path["content_id"], content_type=content_type_list.Any_SLUG) content_type = content_type_list.get_one_by_slug(content.type).slug # TODO - G.M - 2018-08-03 - Jsonify redirect response ? raise HTTPFound( "{base_url}workspaces/{workspace_id}/{content_type}s/{content_id}". format( base_url=BASE_API, workspace_id=content.workspace_id, content_type=content_type, content_id=content.content_id, )) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS]) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(ContentPathInfoSchema()) def get_content_path(self, context, request: TracimRequest, hapic_data=None) -> None: """ Get Content Path : return all hierarchy of content from workspace root to content """ app_config = request.registry.settings["CFG"] # type: CFG api = ContentApi(current_user=request.current_user, session=request.dbsession, config=app_config) content = api.get_one( content_id=hapic_data.path.content_id, content_type=content_type_list.Any_SLUG, workspace=request.current_workspace, ) return ListItemsObject([ api.get_content_in_context(path_content) for path_content in content.content_path ]) @hapic.with_api_doc(tags=[SWAGGER_TAG__CONTENT_ENDPOINTS]) @hapic.handle_exception(WorkspacesDoNotMatch, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ContentFilenameAlreadyUsedInFolder, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(UnallowedSubContent, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ConflictingMoveInItself, HTTPStatus.BAD_REQUEST) @hapic.handle_exception(ConflictingMoveInChild, HTTPStatus.BAD_REQUEST) @check_right(can_move_content) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.input_body(ContentMoveSchema()) @hapic.output_body(ContentDigestSchema()) def move_content(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: """ Move a content to specified new place. This requires to be content manager in both input and output spaces (which may be the same) """ app_config = request.registry.settings["CFG"] # type: CFG path_data = hapic_data.path move_data = hapic_data.body api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(path_data.content_id, content_type=content_type_list.Any_SLUG) new_parent = api.get_one(move_data.new_parent_id, content_type=content_type_list.Any_SLUG) new_workspace = request.candidate_workspace with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.move( content, new_parent=new_parent, new_workspace=new_workspace, new_content_namespace=ContentNamespaces.CONTENT, must_stay_in_same_workspace=False, ) api.execute_update_content_actions(content) updated_content = api.get_one(path_data.content_id, content_type=content_type_list.Any_SLUG) return api.get_content_in_context(updated_content) @hapic.with_api_doc( tags=[SWAGGER_TAG__CONTENT_ALL_TRASH_AND_RESTORE_ENDPOINTS]) @check_right(is_content_manager) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def delete_content(self, context, request: TracimRequest, hapic_data=None) -> None: """ Move a content to the trash. After that, the content will be invisible by default. This action requires the user to be a content manager. Note: the content is still accessible but becomes read-only. """ app_config = request.registry.settings["CFG"] # type: CFG path_data = hapic_data.path api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(path_data.content_id, content_type=content_type_list.Any_SLUG) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.delete(content) api.execute_update_content_actions(content) return @hapic.with_api_doc( tags=[SWAGGER_TAG__CONTENT_ALL_TRASH_AND_RESTORE_ENDPOINTS]) @check_right(is_content_manager) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def undelete_content(self, context, request: TracimRequest, hapic_data=None) -> None: """ Restore a content from the trash. The content will be visible and editable again. """ app_config = request.registry.settings["CFG"] # type: CFG path_data = hapic_data.path api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config, show_deleted=True, show_archived=True, ) content = api.get_one(path_data.content_id, content_type=content_type_list.Any_SLUG) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.undelete(content) api.execute_update_content_actions(content) return @hapic.with_api_doc( tags=[SWAGGER_TAG__CONTENT_ALL_ARCHIVE_AND_RESTORE_ENDPOINTS]) @check_right(is_content_manager) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def archive_content(self, context, request: TracimRequest, hapic_data=None) -> None: """ Archives a content. The content will be invisible but still available. Difference with delete is that optimizing workspace will not delete archived contents This action requires the user to be a content manager. Note: the content is still accessible but becomes read-only. the difference with delete is that optimizing workspace will not delete archived contents """ app_config = request.registry.settings["CFG"] # type: CFG path_data = hapic_data.path api = ContentApi( show_archived=True, show_deleted=True, current_user=request.current_user, session=request.dbsession, config=app_config, ) content = api.get_one(path_data.content_id, content_type=content_type_list.Any_SLUG) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.archive(content) api.execute_update_content_actions(content) return @hapic.with_api_doc( tags=[SWAGGER_TAG__CONTENT_ALL_ARCHIVE_AND_RESTORE_ENDPOINTS]) @check_right(is_content_manager) @hapic.input_path(WorkspaceAndContentIdPathSchema()) @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) def unarchive_content(self, context, request: TracimRequest, hapic_data=None) -> None: """ Restore a content from archive. The content will be visible and editable again. """ app_config = request.registry.settings["CFG"] # type: CFG path_data = hapic_data.path api = ContentApi( current_user=request.current_user, session=request.dbsession, config=app_config, show_archived=True, show_deleted=True, ) content = api.get_one(path_data.content_id, content_type=content_type_list.Any_SLUG) with new_revision(session=request.dbsession, tm=transaction.manager, content=content): api.unarchive(content) api.execute_update_content_actions(content) return def bind(self, configurator: Configurator) -> None: """ Create all routes and views using pyramid configurator for this controller """ # Workspaces configurator.add_route("workspaces", "/workspaces", request_method="GET") configurator.add_view(self.workspaces, route_name="workspaces") # Workspace configurator.add_route("workspace", "/workspaces/{workspace_id}", request_method="GET") configurator.add_view(self.workspace, route_name="workspace") # Workspace space configurator.add_route("workspace_disk_space", "/workspaces/{workspace_id}/disk_space", request_method="GET") configurator.add_view(self.workspace_disk_space, route_name="workspace_disk_space") # Create workspace configurator.add_route("create_workspace", "/workspaces", request_method="POST") configurator.add_view(self.create_workspace, route_name="create_workspace") # Delete/Undelete workpace configurator.add_route("delete_workspace", "/workspaces/{workspace_id}/trashed", request_method="PUT") configurator.add_view(self.delete_workspace, route_name="delete_workspace") configurator.add_route("undelete_workspace", "/workspaces/{workspace_id}/trashed/restore", request_method="PUT") configurator.add_view(self.undelete_workspace, route_name="undelete_workspace") # Update Workspace configurator.add_route("update_workspace", "/workspaces/{workspace_id}", request_method="PUT") configurator.add_view(self.update_workspace, route_name="update_workspace") # Workspace Members (Roles) configurator.add_route("workspace_members", "/workspaces/{workspace_id}/members", request_method="GET") configurator.add_view(self.workspaces_members, route_name="workspace_members") # Workspace Members (Role) Individual configurator.add_route( "workspace_member_role", "/workspaces/{workspace_id}/members/{user_id}", request_method="GET", ) configurator.add_view(self.workspaces_member_role, route_name="workspace_member_role") # Update Workspace Members roles configurator.add_route( "update_workspace_member", "/workspaces/{workspace_id}/members/{user_id}", request_method="PUT", ) configurator.add_view(self.update_workspaces_members_role, route_name="update_workspace_member") # Create Workspace Members roles configurator.add_route("create_workspace_member", "/workspaces/{workspace_id}/members", request_method="POST") configurator.add_view(self.create_workspaces_members_role, route_name="create_workspace_member") # Delete Workspace Members roles configurator.add_route( "delete_workspace_member", "/workspaces/{workspace_id}/members/{user_id}", request_method="DELETE", ) configurator.add_view(self.delete_workspaces_members_role, route_name="delete_workspace_member") # Workspace Content configurator.add_route("workspace_content", "/workspaces/{workspace_id}/contents", request_method="GET") configurator.add_view(self.workspace_content, route_name="workspace_content") # Create Generic Content configurator.add_route("create_generic_content", "/workspaces/{workspace_id}/contents", request_method="POST") configurator.add_view(self.create_generic_empty_content, route_name="create_generic_content") # Get Content configurator.add_route("get_content", "/contents/{content_id}", request_method="GET") configurator.add_view(self.get_content, route_name="get_content") # Get Content From workspace configurator.add_route( "get_content_from_workspace", "/workspaces/{workspace_id}/contents/{content_id}", request_method="GET", ) configurator.add_view(self.get_content_from_workspace, route_name="get_content_from_workspace") # Move Content configurator.add_route( "move_content", "/workspaces/{workspace_id}/contents/{content_id}/move", request_method="PUT", ) configurator.add_view(self.move_content, route_name="move_content") # Delete/Undelete Content configurator.add_route( "delete_content", "/workspaces/{workspace_id}/contents/{content_id}/trashed", request_method="PUT", ) configurator.add_view(self.delete_content, route_name="delete_content") configurator.add_route( "undelete_content", "/workspaces/{workspace_id}/contents/{content_id}/trashed/restore", request_method="PUT", ) configurator.add_view(self.undelete_content, route_name="undelete_content") # # Archive/Unarchive Content configurator.add_route( "archive_content", "/workspaces/{workspace_id}/contents/{content_id}/archived", request_method="PUT", ) configurator.add_view(self.archive_content, route_name="archive_content") configurator.add_route( "unarchive_content", "/workspaces/{workspace_id}/contents/{content_id}/archived/restore", request_method="PUT", ) configurator.add_view(self.unarchive_content, route_name="unarchive_content") # Subscriptions configurator.add_route( "workspace_subscriptions", "/workspaces/{workspace_id}/subscriptions", request_method="GET", ) configurator.add_view(self.workspace_subscriptions, route_name="workspace_subscriptions") configurator.add_route( "accept_subscription", "/workspaces/{workspace_id}/subscriptions/{user_id}/accept", request_method="PUT", ) configurator.add_view(self.accept_subscription, route_name="accept_subscription") configurator.add_route( "reject_subscription", "/workspaces/{workspace_id}/subscriptions/{user_id}/reject", request_method="PUT", ) configurator.add_view(self.reject_subscription, route_name="reject_subscription") # Content path configurator.add_route( "get_content_path", "/workspaces/{workspace_id}/contents/{content_id}/path", request_method="GET", ) configurator.add_view(self.get_content_path, route_name="get_content_path")