Parcourir la source

Merge branch 'develop' of github.com:tracim/tracim_v2 into feature/599_endpoint_redirection_for_content

Guénaël Muller il y a 6 ans
Parent
révision
1bdb21794d
96 fichiers modifiés avec 1438 ajouts et 368 suppressions
  1. 3 0
      .gitignore
  2. 0 1
      .travis.yml
  3. 67 0
      backend/tests_configs.ini
  4. 3 1
      backend/tracim_backend/config.py
  5. 4 0
      backend/tracim_backend/exceptions.py
  6. 40 2
      backend/tracim_backend/lib/core/user.py
  7. 24 0
      backend/tracim_backend/lib/utils/utils.py
  8. 14 2
      backend/tracim_backend/models/applications.py
  9. 6 4
      backend/tracim_backend/models/contents.py
  10. 8 0
      backend/tracim_backend/models/context_models.py
  11. 8 11
      backend/tracim_backend/tests/__init__.py
  12. 24 72
      backend/tracim_backend/tests/functional/test_system.py
  13. 412 15
      backend/tracim_backend/tests/functional/test_user.py
  14. 98 27
      backend/tracim_backend/tests/functional/test_workspaces.py
  15. 183 0
      backend/tracim_backend/tests/library/test_user_api.py
  16. 14 0
      backend/tracim_backend/tests/library/tests_utils.py
  17. 6 0
      backend/tracim_backend/views/contents_api/comment_controller.py
  18. 28 0
      backend/tracim_backend/views/contents_api/file_controller.py
  19. 8 0
      backend/tracim_backend/views/contents_api/html_document_controller.py
  20. 8 0
      backend/tracim_backend/views/contents_api/threads_controller.py
  21. 13 0
      backend/tracim_backend/views/core_api/schemas.py
  22. 57 0
      backend/tracim_backend/views/core_api/user_controller.py
  23. 17 2
      backend/tracim_backend/views/core_api/workspace_controller.py
  24. 1 1
      bash_library.sh
  25. 39 68
      build_full_frontend.sh
  26. 6 0
      color.json.sample
  27. 3 1
      frontend/.gitignore
  28. 0 0
      frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.2.js
  29. 0 0
      frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.css
  30. 0 0
      frontend/dist/asset/bootstrap/jquery-3.2.1.js
  31. 0 0
      frontend/dist/asset/bootstrap/popper-1.12.3.js
  32. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/HELP-US-OUT.txt
  33. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/css/font-awesome.css
  34. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/css/font-awesome.min.css
  35. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/FontAwesome.otf
  36. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.eot
  37. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.svg
  38. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf
  39. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff
  40. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2
  41. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/animated.less
  42. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/bordered-pulled.less
  43. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/core.less
  44. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/fixed-width.less
  45. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/font-awesome.less
  46. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/icons.less
  47. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/larger.less
  48. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/list.less
  49. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/mixins.less
  50. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/path.less
  51. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/rotated-flipped.less
  52. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/screen-reader.less
  53. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/stacked.less
  54. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/less/variables.less
  55. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_animated.scss
  56. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_bordered-pulled.scss
  57. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_core.scss
  58. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_fixed-width.scss
  59. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_icons.scss
  60. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_larger.scss
  61. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_list.scss
  62. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_mixins.scss
  63. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_path.scss
  64. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_rotated-flipped.scss
  65. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_screen-reader.scss
  66. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_stacked.scss
  67. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/_variables.scss
  68. 0 0
      frontend/dist/asset/font/font-awesome-4.7.0/scss/font-awesome.scss
  69. 1 0
      frontend/dist/asset/tracim/appInterface.js
  70. 0 0
      frontend/dist/asset/tracim/tinymceInit.js
  71. BIN
      frontend/dist/ecbb61e619a4d2801db1054c019316cc.jpg
  72. 16 17
      frontend/dist/index.html
  73. 5 12
      frontend/i18next.scanner/en/translation.json
  74. 5 12
      frontend/i18next.scanner/fr/translation.json
  75. 2 5
      frontend/src/component/Workspace/OpenContentApp.jsx
  76. 1 4
      frontend/src/container/Account.jsx
  77. 138 0
      frontend/src/container/AdminWorkspacePage.jsx
  78. 4 7
      frontend/src/container/AppFullscreenManager.jsx
  79. 3 6
      frontend/src/container/Dashboard.jsx
  80. 4 4
      frontend/src/container/Login.jsx
  81. 7 4
      frontend/src/container/Sidebar.jsx
  82. 62 17
      frontend/src/container/Tracim.jsx
  83. 2 5
      frontend/src/container/WorkspaceContent.jsx
  84. 11 0
      frontend/src/css/AdminWorkspacePage.styl
  85. 0 1
      frontend/src/css/Dashboard.styl
  86. 1 1
      frontend/src/css/Generic.styl
  87. 6 0
      frontend/src/css/Login.styl
  88. 0 1
      frontend/src/css/Workspace.styl
  89. 3 5
      frontend/src/helper.js
  90. 1 1
      frontend/src/reducer/user.js
  91. 5 3
      frontend/webpack.config.js
  92. 4 4
      frontend_app_admin_workspace_user/build_admin_workspace_user.sh
  93. 5 4
      frontend_app_html-document/build_html-document.sh
  94. 5 4
      frontend_app_thread/build_thread.sh
  95. 4 4
      frontend_app_workspace/build_workspace.sh
  96. 49 40
      install_frontend_dependencies.sh

+ 3 - 0
.gitignore Voir le fichier

@@ -1,4 +1,7 @@
1 1
 .idea/
2 2
 frontend/dist/app/
3
+frontend/dist/tracim.app.entry.js
4
+frontend/dist/asset/tracim/tracim.vendor.bundle.js
3 5
 frontend_app_html-document/dist/html-document.app.js
4 6
 frontend_lib/dist/tracim_frontend_lib.js
7
+npm-debug.log

+ 0 - 1
.travis.yml Voir le fichier

@@ -3,7 +3,6 @@ matrix:
3 3
     - sudo: false
4 4
       language: python
5 5
       python:
6
-        - "3.4"
7 6
         - "3.5"
8 7
         - "3.6"
9 8
 

+ 67 - 0
backend/tests_configs.ini Voir le fichier

@@ -63,3 +63,70 @@ email.notification.smtp.server = 127.0.0.1
63 63
 email.notification.smtp.port = 1025
64 64
 email.notification.smtp.user = test_user
65 65
 email.notification.smtp.password = just_a_password
66
+
67
+[functional_test]
68
+sqlalchemy.url = sqlite:///tracim_test.sqlite
69
+depot_storage_name = test
70
+depot_storage_dir = /tmp/test/depot
71
+user.auth_token.validity = 604800
72
+preview_cache_dir = /tmp/test/preview_cache
73
+preview.jpg.restricted_dims = True
74
+email.notification.activated = false
75
+
76
+[functional_test_no_db]
77
+sqlalchemy.url = sqlite://
78
+depot_storage_name = test
79
+depot_storage_dir = /tmp/test/depot
80
+user.auth_token.validity = 604800
81
+preview_cache_dir = /tmp/test/preview_cache
82
+preview.jpg.restricted_dims = True
83
+email.notification.activated = false
84
+
85
+[functional_test_with_mail_test_sync]
86
+sqlalchemy.url = sqlite:///tracim_test.sqlite
87
+depot_storage_name = test
88
+depot_storage_dir = /tmp/test/depot
89
+user.auth_token.validity = 604800
90
+preview_cache_dir = /tmp/test/preview_cache
91
+preview.jpg.restricted_dims = True
92
+email.notification.activated = true
93
+email.notification.from.email = test_user_from+{user_id}@localhost
94
+email.notification.from.default_label = Tracim Notifications
95
+email.notification.reply_to.email = test_user_reply+{content_id}@localhost
96
+email.notification.references.email = test_user_refs+{content_id}@localhost
97
+email.notification.content_update.template.html = %(here)s/tracim_backend/templates/mail/content_update_body_html.mak
98
+email.notification.content_update.template.text = %(here)s/tracim_backend/templates/mail/content_update_body_text.mak
99
+email.notification.created_account.template.html = %(here)s/tracim_backend/templates/mail/created_account_body_html.mak
100
+email.notification.created_account.template.text = %(here)s/tracim_backend/templates/mail/created_account_body_text.mak
101
+email.notification.content_update.subject = [{website_title}] [{workspace_label}] {content_label} ({content_status_label})
102
+email.notification.created_account.subject = [{website_title}] Created account
103
+email.notification.processing_mode = sync
104
+email.notification.smtp.server = 127.0.0.1
105
+email.notification.smtp.port = 1025
106
+email.notification.smtp.user = test_user
107
+email.notification.smtp.password = just_a_password
108
+
109
+
110
+[functional_test_with_mail_test_async]
111
+sqlalchemy.url = sqlite:///tracim_test.sqlite
112
+depot_storage_name = test
113
+depot_storage_dir = /tmp/test/depot
114
+user.auth_token.validity = 604800
115
+preview_cache_dir = /tmp/test/preview_cache
116
+preview.jpg.restricted_dims = True
117
+email.notification.activated = true
118
+email.notification.from.email = test_user_from+{user_id}@localhost
119
+email.notification.from.default_label = Tracim Notifications
120
+email.notification.reply_to.email = test_user_reply+{content_id}@localhost
121
+email.notification.references.email = test_user_refs+{content_id}@localhost
122
+email.notification.content_update.template.html = %(here)s/tracim_backend/templates/mail/content_update_body_html.mak
123
+email.notification.content_update.template.text = %(here)s/tracim_backend/templates/mail/content_update_body_text.mak
124
+email.notification.created_account.template.html = %(here)s/tracim_backend/templates/mail/created_account_body_html.mak
125
+email.notification.created_account.template.text = %(here)s/tracim_backend/templates/mail/created_account_body_text.mak
126
+email.notification.content_update.subject = [{website_title}] [{workspace_label}] {content_label} ({content_status_label})
127
+email.notification.created_account.subject = [{website_title}] Created account
128
+email.notification.processing_mode = async
129
+email.notification.smtp.server = 127.0.0.1
130
+email.notification.smtp.port = 1025
131
+email.notification.smtp.user = test_user
132
+email.notification.smtp.password = just_a_password

+ 3 - 1
backend/tracim_backend/config.py Voir le fichier

@@ -161,9 +161,11 @@ class CFG(object):
161 161
 
162 162
         self.EMAIL_NOTIFICATION_FROM_EMAIL = settings.get(
163 163
             'email.notification.from.email',
164
+            'noreply+{user_id}@trac.im'
164 165
         )
165 166
         self.EMAIL_NOTIFICATION_FROM_DEFAULT_LABEL = settings.get(
166
-            'email.notification.from.default_label'
167
+            'email.notification.from.default_label',
168
+            'Tracim Notifications'
167 169
         )
168 170
         self.EMAIL_NOTIFICATION_REPLY_TO_EMAIL = settings.get(
169 171
             'email.notification.reply_to.email',

+ 4 - 0
backend/tracim_backend/exceptions.py Voir le fichier

@@ -203,3 +203,7 @@ class PageOfPreviewNotFound(NotFound):
203 203
 
204 204
 class PreviewDimNotAllowed(TracimException):
205 205
     pass
206
+
207
+
208
+class TooShortAutocompleteString(TracimException):
209
+    pass

+ 40 - 2
backend/tracim_backend/lib/core/user.py Voir le fichier

@@ -3,13 +3,17 @@ from smtplib import SMTPException
3 3
 
4 4
 import transaction
5 5
 import typing as typing
6
+
7
+from sqlalchemy import or_
6 8
 from sqlalchemy.orm import Session
9
+from sqlalchemy.orm import Query
7 10
 from sqlalchemy.orm.exc import NoResultFound
8 11
 
9 12
 from tracim_backend import CFG
10 13
 from tracim_backend.models.auth import User
11 14
 from tracim_backend.models.auth import Group
12 15
 from tracim_backend.exceptions import NoUserSetted
16
+from tracim_backend.exceptions import TooShortAutocompleteString
13 17
 from tracim_backend.exceptions import PasswordDoNotMatch
14 18
 from tracim_backend.exceptions import EmailValidationFailed
15 19
 from tracim_backend.exceptions import UserDoesNotExist
@@ -20,6 +24,7 @@ from tracim_backend.exceptions import UserNotActive
20 24
 from tracim_backend.models.context_models import UserInContext
21 25
 from tracim_backend.lib.mail_notifier.notifier import get_email_manager
22 26
 from tracim_backend.models.context_models import TypeUser
27
+from tracim_backend.models.data import UserRoleInWorkspace
23 28
 
24 29
 
25 30
 class UserApi(object):
@@ -94,8 +99,41 @@ class UserApi(object):
94 99
             raise UserDoesNotExist('There is no current user')
95 100
         return self._user
96 101
 
102
+    def _get_all_query(self) -> Query:
103
+        return self._session.query(User).order_by(User.display_name)
104
+
97 105
     def get_all(self) -> typing.Iterable[User]:
98
-        return self._session.query(User).order_by(User.display_name).all()
106
+        return self._get_all_query().all()
107
+
108
+    def get_known_user(
109
+            self,
110
+            acp: str,
111
+    ) -> typing.Iterable[User]:
112
+        """
113
+        Return list of know user by current UserApi user.
114
+        :param acp: autocomplete filter by name/email
115
+        :return: List of found users
116
+        """
117
+        if len(acp) < 2:
118
+            raise TooShortAutocompleteString(
119
+                '"{acp}" is a too short string, acp string need to have more than one character'.format(acp=acp)  # nopep8
120
+            )
121
+        query = self._get_all_query()
122
+        query = query.filter(or_(User.display_name.ilike('%{}%'.format(acp)), User.email.ilike('%{}%'.format(acp))))  # nopep8
123
+
124
+        # INFO - G.M - 2018-07-27 - if user is set and is simple user, we
125
+        # should show only user in same workspace as user
126
+        if self._user and self._user.profile.id <= Group.TIM_USER:
127
+            user_workspaces_id_query = self._session.\
128
+                query(UserRoleInWorkspace.workspace_id).\
129
+                distinct(UserRoleInWorkspace.workspace_id).\
130
+                filter(UserRoleInWorkspace.user_id == self._user.user_id)
131
+            users_in_workspaces = self._session.\
132
+                query(UserRoleInWorkspace.user_id).\
133
+                distinct(UserRoleInWorkspace.user_id).\
134
+                filter(UserRoleInWorkspace.workspace_id.in_(user_workspaces_id_query.subquery())).subquery()  # nopep8
135
+            query = query.filter(User.user_id.in_(users_in_workspaces))
136
+        return query.all()
99 137
 
100 138
     def find(
101 139
             self,
@@ -196,7 +234,7 @@ class UserApi(object):
196 234
         )
197 235
         if do_save:
198 236
             # TODO - G.M - 2018-07-24 - Check why commit is needed here
199
-            transaction.commit()
237
+            self.save(user)
200 238
         return user
201 239
 
202 240
     def set_email(

+ 24 - 0
backend/tracim_backend/lib/utils/utils.py Voir le fichier

@@ -1,5 +1,7 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 import datetime
3
+import random
4
+import string
3 5
 from redis import Redis
4 6
 from rq import Queue
5 7
 
@@ -72,3 +74,25 @@ def current_date_for_filename() -> str:
72 74
     # webdav utils, it may cause trouble. So, it should be replaced to
73 75
     # a character which will not change in bdd.
74 76
     return datetime.datetime.now().isoformat().replace(':', '.')
77
+
78
+# INFO - G.M - 2018-08-02 - Simple password generator, inspired by
79
+# https://gist.github.com/23maverick23/4131896
80
+
81
+
82
+ALLOWED_AUTOGEN_PASSWORD_CHAR = string.ascii_letters + \
83
+                                string.digits + \
84
+                                string.punctuation
85
+
86
+DEFAULT_PASSWORD_GEN_CHAR_LENGTH = 12
87
+
88
+
89
+def password_generator(
90
+        length: int=DEFAULT_PASSWORD_GEN_CHAR_LENGTH,
91
+        chars: str=ALLOWED_AUTOGEN_PASSWORD_CHAR
92
+) -> str:
93
+    """
94
+    :param length: length of the new password
95
+    :param chars: characters allowed
96
+    :return: password as string
97
+    """
98
+    return ''.join(random.choice(chars) for char_number in range(length))

+ 14 - 2
backend/tracim_backend/models/applications.py Voir le fichier

@@ -59,6 +59,16 @@ thread = Application(
59 59
 
60 60
 )
61 61
 
62
+folder = Application(
63
+    label='Folder',
64
+    slug='contents/folder',
65
+    fa_icon='folder-open-o',
66
+    hexcolor='#252525',
67
+    is_active=True,
68
+    config={},
69
+    main_route='',
70
+)
71
+
62 72
 _file = Application(
63 73
     label='Files',
64 74
     slug='contents/file',
@@ -92,8 +102,10 @@ html_documents = Application(
92 102
 # List of applications
93 103
 applications = [
94 104
     html_documents,
95
-    markdownpluspage,
105
+    # TODO - G.M - 2018-08-02 - Restore markdownpage app
106
+    # markdownpluspage,
96 107
     _file,
97 108
     thread,
98
-    calendar,
109
+    folder,
110
+    # calendar,
99 111
 ]

+ 6 - 4
backend/tracim_backend/models/contents.py Voir le fichier

@@ -6,6 +6,7 @@ from tracim_backend.exceptions import ContentTypeNotExist
6 6
 from tracim_backend.exceptions import ContentStatusNotExist
7 7
 from tracim_backend.models.applications import html_documents
8 8
 from tracim_backend.models.applications import _file
9
+from tracim_backend.models.applications import folder
9 10
 from tracim_backend.models.applications import thread
10 11
 from tracim_backend.models.applications import markdownpluspage
11 12
 
@@ -171,10 +172,10 @@ html_documents_type = ContentType(
171 172
 # TODO - G.M - 31-05-2018 - Set Better folder params
172 173
 folder_type = ContentType(
173 174
     slug='folder',
174
-    fa_icon=thread.fa_icon,
175
-    hexcolor=thread.hexcolor,
175
+    fa_icon=folder.fa_icon,
176
+    hexcolor=folder.hexcolor,
176 177
     label='Folder',
177
-    creation_label='Create collection of any documents',
178
+    creation_label='Create a folder',
178 179
     available_statuses=CONTENT_STATUS.get_all(),
179 180
 )
180 181
 
@@ -261,7 +262,8 @@ CONTENT_TYPES = ContentTypeList(
261 262
     [
262 263
         thread_type,
263 264
         file_type,
264
-        markdownpluspage_type,
265
+        # TODO - G.M - 2018-08-02 - Restore markdown page content
266
+        #    markdownpluspage_type,
265 267
         html_documents_type,
266 268
     ]
267 269
 )

+ 8 - 0
backend/tracim_backend/models/context_models.py Voir le fichier

@@ -186,6 +186,14 @@ class CommentPath(object):
186 186
         self.comment_id = comment_id
187 187
 
188 188
 
189
+class AutocompleteQuery(object):
190
+    """
191
+    Autocomplete query model
192
+    """
193
+    def __init__(self, acp: str):
194
+        self.acp = acp
195
+
196
+
189 197
 class PageQuery(object):
190 198
     """
191 199
     Page query model

+ 8 - 11
backend/tracim_backend/tests/__init__.py Voir le fichier

@@ -68,20 +68,17 @@ def create_1000px_png_test_image():
68 68
 class FunctionalTest(unittest.TestCase):
69 69
 
70 70
     fixtures = [BaseFixture]
71
-    sqlalchemy_url = 'sqlite:///tracim_test.sqlite'
71
+    config_uri = 'tests_configs.ini'
72
+    config_section = 'functional_test'
72 73
 
73 74
     def setUp(self):
74 75
         logger._logger.setLevel('WARNING')
76
+
75 77
         DepotManager._clear()
76
-        self.settings = {
77
-            'sqlalchemy.url': self.sqlalchemy_url,
78
-            'user.auth_token.validity': '604800',
79
-            'depot_storage_dir': '/tmp/test/depot',
80
-            'depot_storage_name': 'test',
81
-            'preview_cache_dir': '/tmp/test/preview_cache',
82
-            'preview.jpg.restricted_dims': True,
83
-            'email.notification.activated': 'false',
84
-        }
78
+        self.settings = plaster.get_settings(
79
+            self.config_uri,
80
+            self.config_section
81
+        )
85 82
         hapic.reset_context()
86 83
         self.engine = get_engine(self.settings)
87 84
         DeclarativeBase.metadata.create_all(self.engine)
@@ -127,7 +124,7 @@ class FunctionalTestEmptyDB(FunctionalTest):
127 124
 
128 125
 
129 126
 class FunctionalTestNoDB(FunctionalTest):
130
-    sqlalchemy_url = 'sqlite://'
127
+    config_section = 'functional_test_no_db'
131 128
 
132 129
     def init_database(self, settings):
133 130
         self.engine = get_engine(settings)

+ 24 - 72
backend/tracim_backend/tests/functional/test_system.py Voir le fichier

@@ -1,5 +1,7 @@
1 1
 # coding=utf-8
2
+from tracim_backend.models.contents import CONTENT_TYPES
2 3
 from tracim_backend.tests import FunctionalTest
4
+from tracim_backend.models.applications import applications
3 5
 
4 6
 """
5 7
 Tests for /api/v2/system subpath endpoints.
@@ -24,41 +26,14 @@ class TestApplicationEndpoint(FunctionalTest):
24 26
         )
25 27
         res = self.testapp.get('/api/v2/system/applications', status=200)
26 28
         res = res.json_body
27
-        application = res[0]
28
-        assert application['label'] == "Text Documents"
29
-        assert application['slug'] == 'contents/html-document'
30
-        assert application['fa_icon'] == 'file-text-o'
31
-        assert application['hexcolor'] == '#3f52e3'
32
-        assert application['is_active'] is True
33
-        assert 'config' in application
34
-        application = res[1]
35
-        assert application['label'] == "Markdown Plus Documents"
36
-        assert application['slug'] == 'contents/markdownpluspage'
37
-        assert application['fa_icon'] == 'file-code-o'
38
-        assert application['hexcolor'] == '#f12d2d'
39
-        assert application['is_active'] is True
40
-        assert 'config' in application
41
-        application = res[2]
42
-        assert application['label'] == "Files"
43
-        assert application['slug'] == 'contents/file'
44
-        assert application['fa_icon'] == 'paperclip'
45
-        assert application['hexcolor'] == '#FF9900'
46
-        assert application['is_active'] is True
47
-        assert 'config' in application
48
-        application = res[3]
49
-        assert application['label'] == "Threads"
50
-        assert application['slug'] == 'contents/thread'
51
-        assert application['fa_icon'] == 'comments-o'
52
-        assert application['hexcolor'] == '#ad4cf9'
53
-        assert application['is_active'] is True
54
-        assert 'config' in application
55
-        application = res[4]
56
-        assert application['label'] == "Calendar"
57
-        assert application['slug'] == 'calendar'
58
-        assert application['fa_icon'] == 'calendar'
59
-        assert application['hexcolor'] == '#757575'
60
-        assert application['is_active'] is True
61
-        assert 'config' in application
29
+        assert len(res) == len(applications)
30
+        for counter, application in enumerate(applications):
31
+            assert res[counter]['label'] == application.label
32
+            assert res[counter]['slug'] == application.slug
33
+            assert res[counter]['fa_icon'] == application.fa_icon
34
+            assert res[counter]['hexcolor'] == application.hexcolor
35
+            assert res[counter]['is_active'] == application.is_active
36
+            assert res[counter]['config'] == application.config
62 37
 
63 38
     def test_api__get_applications__err_401__unregistered_user(self):
64 39
         """
@@ -96,44 +71,21 @@ class TestContentsTypesEndpoint(FunctionalTest):
96 71
         )
97 72
         res = self.testapp.get('/api/v2/system/content_types', status=200)
98 73
         res = res.json_body
74
+        assert len(res) == len(CONTENT_TYPES.endpoint_allowed_types_slug())
75
+        content_types = CONTENT_TYPES.endpoint_allowed_types_slug()
99 76
 
100
-        content_type = res[1]
101
-        assert content_type['slug'] == 'thread'
102
-        assert content_type['fa_icon'] == 'comments-o'
103
-        assert content_type['hexcolor'] == '#ad4cf9'
104
-        assert content_type['label'] == 'Thread'
105
-        assert content_type['creation_label'] == 'Discuss about a topic'
106
-        assert 'available_statuses' in content_type
107
-        assert len(content_type['available_statuses']) == 4
108
-
109
-        content_type = res[2]
110
-        assert content_type['slug'] == 'file'
111
-        assert content_type['fa_icon'] == 'paperclip'
112
-        assert content_type['hexcolor'] == '#FF9900'
113
-        assert content_type['label'] == 'File'
114
-        assert content_type['creation_label'] == 'Upload a file'
115
-        assert 'available_statuses' in content_type
116
-        assert len(content_type['available_statuses']) == 4
117
-
118
-        content_type = res[3]
119
-        assert content_type['slug'] == 'markdownpage'
120
-        assert content_type['fa_icon'] == 'file-code-o'
121
-        assert content_type['hexcolor'] == '#f12d2d'
122
-        assert content_type['label'] == 'Rich Markdown File'
123
-        assert content_type['creation_label'] == 'Create a Markdown document'
124
-        assert 'available_statuses' in content_type
125
-        assert len(content_type['available_statuses']) == 4
126
-
127
-        content_type = res[4]
128
-        assert content_type['slug'] == 'html-document'
129
-        assert content_type['fa_icon'] == 'file-text-o'
130
-        assert content_type['hexcolor'] == '#3f52e3'
131
-        assert content_type['label'] == 'Text Document'
132
-        assert content_type['creation_label'] == 'Write a document'
133
-        assert 'available_statuses' in content_type
134
-        assert len(content_type['available_statuses']) == 4
135
-        # TODO - G.M - 31-05-2018 - Check Folder type
136
-        # TODO - G.M - 29-05-2018 - Better check for available_statuses
77
+        for counter, content_type_slug in enumerate(content_types):
78
+            content_type = CONTENT_TYPES.get_one_by_slug(content_type_slug)
79
+            assert res[counter]['slug'] == content_type.slug
80
+            assert res[counter]['fa_icon'] == content_type.fa_icon
81
+            assert res[counter]['hexcolor'] == content_type.hexcolor
82
+            assert res[counter]['label'] == content_type.label
83
+            assert res[counter]['creation_label'] == content_type.creation_label
84
+            for status_counter, status in enumerate(content_type.available_statuses):
85
+                assert res[counter]['available_statuses'][status_counter]['fa_icon'] == status.fa_icon  # nopep8
86
+                assert res[counter]['available_statuses'][status_counter]['global_status'] == status.global_status  # nopep8
87
+                assert res[counter]['available_statuses'][status_counter]['slug'] == status.slug  # nopep8
88
+                assert res[counter]['available_statuses'][status_counter]['hexcolor'] == status.hexcolor  # nopep8
137 89
 
138 90
     def test_api__get_content_types__err_401__unregistered_user(self):
139 91
         """

+ 412 - 15
backend/tracim_backend/tests/functional/test_user.py Voir le fichier

@@ -2382,8 +2382,10 @@ class TestUserWorkspaceEndpoint(FunctionalTest):
2382 2382
         assert workspace['workspace_id'] == 1
2383 2383
         assert workspace['label'] == 'Business'
2384 2384
         assert workspace['slug'] == 'business'
2385
-        assert len(workspace['sidebar_entries']) == 7
2385
+        assert len(workspace['sidebar_entries']) == 5
2386 2386
 
2387
+        # TODO - G.M - 2018-08-02 - Better test for sidebar entry, make it
2388
+        # not fixed on active application/content-file
2387 2389
         sidebar_entry = workspace['sidebar_entries'][0]
2388 2390
         assert sidebar_entry['slug'] == 'dashboard'
2389 2391
         assert sidebar_entry['label'] == 'Dashboard'
@@ -2406,32 +2408,19 @@ class TestUserWorkspaceEndpoint(FunctionalTest):
2406 2408
         assert sidebar_entry['fa_icon'] == "file-text-o"
2407 2409
 
2408 2410
         sidebar_entry = workspace['sidebar_entries'][3]
2409
-        assert sidebar_entry['slug'] == 'contents/markdownpluspage'
2410
-        assert sidebar_entry['label'] == 'Markdown Plus Documents'
2411
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=markdownpluspage"    # nopep8
2412
-        assert sidebar_entry['hexcolor'] == "#f12d2d"
2413
-        assert sidebar_entry['fa_icon'] == "file-code-o"
2414
-
2415
-        sidebar_entry = workspace['sidebar_entries'][4]
2416 2411
         assert sidebar_entry['slug'] == 'contents/file'
2417 2412
         assert sidebar_entry['label'] == 'Files'
2418 2413
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
2419 2414
         assert sidebar_entry['hexcolor'] == "#FF9900"
2420 2415
         assert sidebar_entry['fa_icon'] == "paperclip"
2421 2416
 
2422
-        sidebar_entry = workspace['sidebar_entries'][5]
2417
+        sidebar_entry = workspace['sidebar_entries'][4]
2423 2418
         assert sidebar_entry['slug'] == 'contents/thread'
2424 2419
         assert sidebar_entry['label'] == 'Threads'
2425 2420
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
2426 2421
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
2427 2422
         assert sidebar_entry['fa_icon'] == "comments-o"
2428 2423
 
2429
-        sidebar_entry = workspace['sidebar_entries'][6]
2430
-        assert sidebar_entry['slug'] == 'calendar'
2431
-        assert sidebar_entry['label'] == 'Calendar'
2432
-        assert sidebar_entry['route'] == "/#/workspaces/1/calendar"  # nopep8
2433
-        assert sidebar_entry['hexcolor'] == "#757575"
2434
-        assert sidebar_entry['fa_icon'] == "calendar"
2435 2424
 
2436 2425
     def test_api__get_user_workspaces__err_403__unallowed_user(self):
2437 2426
         """
@@ -2646,6 +2635,390 @@ class TestUserEndpoint(FunctionalTest):
2646 2635
         )
2647 2636
 
2648 2637
 
2638
+class TestUsersEndpoint(FunctionalTest):
2639
+    # -*- coding: utf-8 -*-
2640
+    """
2641
+    Tests for GET /api/v2/users/{user_id}
2642
+    """
2643
+    fixtures = [BaseFixture]
2644
+
2645
+    def test_api__get_user__ok_200__admin(self):
2646
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2647
+        admin = dbsession.query(models.User) \
2648
+            .filter(models.User.email == 'admin@admin.admin') \
2649
+            .one()
2650
+        uapi = UserApi(
2651
+            current_user=admin,
2652
+            session=dbsession,
2653
+            config=self.app_config,
2654
+        )
2655
+        gapi = GroupApi(
2656
+            current_user=admin,
2657
+            session=dbsession,
2658
+            config=self.app_config,
2659
+        )
2660
+        groups = [gapi.get_one_with_name('users')]
2661
+        test_user = uapi.create_user(
2662
+            email='test@test.test',
2663
+            password='pass',
2664
+            name='bob',
2665
+            groups=groups,
2666
+            timezone='Europe/Paris',
2667
+            do_save=True,
2668
+            do_notify=False,
2669
+        )
2670
+        uapi.save(test_user)
2671
+        transaction.commit()
2672
+        user_id = int(test_user.user_id)
2673
+
2674
+        self.testapp.authorization = (
2675
+            'Basic',
2676
+            (
2677
+                'admin@admin.admin',
2678
+                'admin@admin.admin'
2679
+            )
2680
+        )
2681
+        res = self.testapp.get(
2682
+            '/api/v2/users',
2683
+            status=200
2684
+        )
2685
+        res = res.json_body
2686
+        assert len(res) == 2
2687
+        assert res[0]['user_id'] == admin.user_id
2688
+        assert res[0]['public_name'] == admin.display_name
2689
+        assert res[0]['avatar_url'] is None
2690
+
2691
+        assert res[1]['user_id'] == test_user.user_id
2692
+        assert res[1]['public_name'] == test_user.display_name
2693
+        assert res[1]['avatar_url'] is None
2694
+
2695
+    def test_api__get_user__err_403__normal_user(self):
2696
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2697
+        admin = dbsession.query(models.User) \
2698
+            .filter(models.User.email == 'admin@admin.admin') \
2699
+            .one()
2700
+        uapi = UserApi(
2701
+            current_user=admin,
2702
+            session=dbsession,
2703
+            config=self.app_config,
2704
+        )
2705
+        gapi = GroupApi(
2706
+            current_user=admin,
2707
+            session=dbsession,
2708
+            config=self.app_config,
2709
+        )
2710
+        groups = [gapi.get_one_with_name('users')]
2711
+        test_user = uapi.create_user(
2712
+            email='test@test.test',
2713
+            password='pass',
2714
+            name='bob',
2715
+            groups=groups,
2716
+            timezone='Europe/Paris',
2717
+            do_save=True,
2718
+            do_notify=False,
2719
+        )
2720
+        uapi.save(test_user)
2721
+        transaction.commit()
2722
+        user_id = int(test_user.user_id)
2723
+
2724
+        self.testapp.authorization = (
2725
+            'Basic',
2726
+            (
2727
+                'test@test.test',
2728
+                'pass'
2729
+            )
2730
+        )
2731
+        self.testapp.get(
2732
+            '/api/v2/users',
2733
+            status=403
2734
+        )
2735
+
2736
+
2737
+class TestKnownMembersEndpoint(FunctionalTest):
2738
+    # -*- coding: utf-8 -*-
2739
+    """
2740
+    Tests for GET /api/v2/users/{user_id}
2741
+    """
2742
+    fixtures = [BaseFixture]
2743
+
2744
+    def test_api__get_user__ok_200__admin__by_name(self):
2745
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2746
+        admin = dbsession.query(models.User) \
2747
+            .filter(models.User.email == 'admin@admin.admin') \
2748
+            .one()
2749
+        uapi = UserApi(
2750
+            current_user=admin,
2751
+            session=dbsession,
2752
+            config=self.app_config,
2753
+        )
2754
+        gapi = GroupApi(
2755
+            current_user=admin,
2756
+            session=dbsession,
2757
+            config=self.app_config,
2758
+        )
2759
+        groups = [gapi.get_one_with_name('users')]
2760
+        test_user = uapi.create_user(
2761
+            email='test@test.test',
2762
+            password='pass',
2763
+            name='bob',
2764
+            groups=groups,
2765
+            timezone='Europe/Paris',
2766
+            do_save=True,
2767
+            do_notify=False,
2768
+        )
2769
+        test_user2 = uapi.create_user(
2770
+            email='test2@test2.test2',
2771
+            password='pass',
2772
+            name='bob2',
2773
+            groups=groups,
2774
+            timezone='Europe/Paris',
2775
+            do_save=True,
2776
+            do_notify=False,
2777
+        )
2778
+        uapi.save(test_user)
2779
+        uapi.save(test_user2)
2780
+        transaction.commit()
2781
+        user_id = int(admin.user_id)
2782
+
2783
+        self.testapp.authorization = (
2784
+            'Basic',
2785
+            (
2786
+                'admin@admin.admin',
2787
+                'admin@admin.admin'
2788
+            )
2789
+        )
2790
+        params = {
2791
+            'acp': 'bob',
2792
+        }
2793
+        res = self.testapp.get(
2794
+            '/api/v2/users/{user_id}/known_members'.format(user_id=user_id),
2795
+            status=200,
2796
+            params=params,
2797
+        )
2798
+        res = res.json_body
2799
+        assert len(res) == 2
2800
+        assert res[0]['user_id'] == test_user.user_id
2801
+        assert res[0]['public_name'] == test_user.display_name
2802
+        assert res[0]['avatar_url'] is None
2803
+
2804
+        assert res[1]['user_id'] == test_user2.user_id
2805
+        assert res[1]['public_name'] == test_user2.display_name
2806
+        assert res[1]['avatar_url'] is None
2807
+
2808
+    def test_api__get_user__ok_200__admin__by_email(self):
2809
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2810
+        admin = dbsession.query(models.User) \
2811
+            .filter(models.User.email == 'admin@admin.admin') \
2812
+            .one()
2813
+        uapi = UserApi(
2814
+            current_user=admin,
2815
+            session=dbsession,
2816
+            config=self.app_config,
2817
+        )
2818
+        gapi = GroupApi(
2819
+            current_user=admin,
2820
+            session=dbsession,
2821
+            config=self.app_config,
2822
+        )
2823
+        groups = [gapi.get_one_with_name('users')]
2824
+        test_user = uapi.create_user(
2825
+            email='test@test.test',
2826
+            password='pass',
2827
+            name='bob',
2828
+            groups=groups,
2829
+            timezone='Europe/Paris',
2830
+            do_save=True,
2831
+            do_notify=False,
2832
+        )
2833
+        test_user2 = uapi.create_user(
2834
+            email='test2@test2.test2',
2835
+            password='pass',
2836
+            name='bob2',
2837
+            groups=groups,
2838
+            timezone='Europe/Paris',
2839
+            do_save=True,
2840
+            do_notify=False,
2841
+        )
2842
+        uapi.save(test_user)
2843
+        uapi.save(test_user2)
2844
+        transaction.commit()
2845
+        user_id = int(admin.user_id)
2846
+
2847
+        self.testapp.authorization = (
2848
+            'Basic',
2849
+            (
2850
+                'admin@admin.admin',
2851
+                'admin@admin.admin'
2852
+            )
2853
+        )
2854
+        params = {
2855
+            'acp': 'test',
2856
+        }
2857
+        res = self.testapp.get(
2858
+            '/api/v2/users/{user_id}/known_members'.format(user_id=user_id),
2859
+            status=200,
2860
+            params=params,
2861
+        )
2862
+        res = res.json_body
2863
+        assert len(res) == 2
2864
+        assert res[0]['user_id'] == test_user.user_id
2865
+        assert res[0]['public_name'] == test_user.display_name
2866
+        assert res[0]['avatar_url'] is None
2867
+
2868
+        assert res[1]['user_id'] == test_user2.user_id
2869
+        assert res[1]['public_name'] == test_user2.display_name
2870
+        assert res[1]['avatar_url'] is None
2871
+
2872
+    def test_api__get_user__err_403__admin__too_small_acp(self):
2873
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2874
+        admin = dbsession.query(models.User) \
2875
+            .filter(models.User.email == 'admin@admin.admin') \
2876
+            .one()
2877
+        uapi = UserApi(
2878
+            current_user=admin,
2879
+            session=dbsession,
2880
+            config=self.app_config,
2881
+        )
2882
+        gapi = GroupApi(
2883
+            current_user=admin,
2884
+            session=dbsession,
2885
+            config=self.app_config,
2886
+        )
2887
+        groups = [gapi.get_one_with_name('users')]
2888
+        test_user = uapi.create_user(
2889
+            email='test@test.test',
2890
+            password='pass',
2891
+            name='bob',
2892
+            groups=groups,
2893
+            timezone='Europe/Paris',
2894
+            do_save=True,
2895
+            do_notify=False,
2896
+        )
2897
+        test_user2 = uapi.create_user(
2898
+            email='test2@test2.test2',
2899
+            password='pass',
2900
+            name='bob2',
2901
+            groups=groups,
2902
+            timezone='Europe/Paris',
2903
+            do_save=True,
2904
+            do_notify=False,
2905
+        )
2906
+        uapi.save(test_user)
2907
+        transaction.commit()
2908
+        user_id = int(admin.user_id)
2909
+
2910
+        self.testapp.authorization = (
2911
+            'Basic',
2912
+            (
2913
+                'admin@admin.admin',
2914
+                'admin@admin.admin'
2915
+            )
2916
+        )
2917
+        params = {
2918
+            'acp': 't',
2919
+        }
2920
+        res = self.testapp.get(
2921
+            '/api/v2/users/{user_id}/known_members'.format(user_id=user_id),
2922
+            status=400,
2923
+            params=params
2924
+        )
2925
+
2926
+    def test_api__get_user__ok_200__normal_user_by_email(self):
2927
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
2928
+        admin = dbsession.query(models.User) \
2929
+            .filter(models.User.email == 'admin@admin.admin') \
2930
+            .one()
2931
+        uapi = UserApi(
2932
+            current_user=admin,
2933
+            session=dbsession,
2934
+            config=self.app_config,
2935
+        )
2936
+        gapi = GroupApi(
2937
+            current_user=admin,
2938
+            session=dbsession,
2939
+            config=self.app_config,
2940
+        )
2941
+        groups = [gapi.get_one_with_name('users')]
2942
+        test_user = uapi.create_user(
2943
+            email='test@test.test',
2944
+            password='pass',
2945
+            name='bob',
2946
+            groups=groups,
2947
+            timezone='Europe/Paris',
2948
+            do_save=True,
2949
+            do_notify=False,
2950
+        )
2951
+        test_user2 = uapi.create_user(
2952
+            email='test2@test2.test2',
2953
+            password='pass',
2954
+            name='bob2',
2955
+            groups=groups,
2956
+            timezone='Europe/Paris',
2957
+            do_save=True,
2958
+            do_notify=False,
2959
+        )
2960
+        test_user3 = uapi.create_user(
2961
+            email='test3@test3.test3',
2962
+            password='pass',
2963
+            name='bob3',
2964
+            groups=groups,
2965
+            timezone='Europe/Paris',
2966
+            do_save=True,
2967
+            do_notify=False,
2968
+        )
2969
+        uapi.save(test_user)
2970
+        uapi.save(test_user2)
2971
+        uapi.save(test_user3)
2972
+        workspace_api = WorkspaceApi(
2973
+            current_user=admin,
2974
+            session=dbsession,
2975
+            config=self.app_config
2976
+
2977
+        )
2978
+        workspace = WorkspaceApi(
2979
+            current_user=admin,
2980
+            session=dbsession,
2981
+            config=self.app_config,
2982
+        ).create_workspace(
2983
+            'test workspace',
2984
+            save_now=True
2985
+        )
2986
+        role_api = RoleApi(
2987
+            current_user=admin,
2988
+            session=dbsession,
2989
+            config=self.app_config,
2990
+        )
2991
+        role_api.create_one(test_user, workspace, UserRoleInWorkspace.READER, False)
2992
+        role_api.create_one(test_user2, workspace, UserRoleInWorkspace.READER, False)
2993
+        transaction.commit()
2994
+        user_id = int(test_user.user_id)
2995
+
2996
+        self.testapp.authorization = (
2997
+            'Basic',
2998
+            (
2999
+                'test@test.test',
3000
+                'pass'
3001
+            )
3002
+        )
3003
+        params = {
3004
+            'acp': 'test',
3005
+        }
3006
+        res = self.testapp.get(
3007
+            '/api/v2/users/{user_id}/known_members'.format(user_id=user_id),
3008
+            status=200,
3009
+            params=params
3010
+        )
3011
+        res = res.json_body
3012
+        assert len(res) == 2
3013
+        assert res[0]['user_id'] == test_user.user_id
3014
+        assert res[0]['public_name'] == test_user.display_name
3015
+        assert res[0]['avatar_url'] is None
3016
+
3017
+        assert res[1]['user_id'] == test_user2.user_id
3018
+        assert res[1]['public_name'] == test_user2.display_name
3019
+        assert res[1]['avatar_url'] is None
3020
+
3021
+
2649 3022
 class TestSetEmailEndpoint(FunctionalTest):
2650 3023
     # -*- coding: utf-8 -*-
2651 3024
     """
@@ -3025,6 +3398,12 @@ class TestSetPasswordEndpoint(FunctionalTest):
3025 3398
             status=204,
3026 3399
         )
3027 3400
         # Check After
3401
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3402
+        uapi = UserApi(
3403
+            current_user=admin,
3404
+            session=dbsession,
3405
+            config=self.app_config,
3406
+        )
3028 3407
         user = uapi.get_one(user_id)
3029 3408
         assert not user.validate_password('pass')
3030 3409
         assert user.validate_password('mynewpassword')
@@ -3080,6 +3459,12 @@ class TestSetPasswordEndpoint(FunctionalTest):
3080 3459
             params=params,
3081 3460
             status=403,
3082 3461
         )
3462
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3463
+        uapi = UserApi(
3464
+            current_user=admin,
3465
+            session=dbsession,
3466
+            config=self.app_config,
3467
+        )
3083 3468
         # Check After
3084 3469
         user = uapi.get_one(user_id)
3085 3470
         assert user.validate_password('pass')
@@ -3138,6 +3523,12 @@ class TestSetPasswordEndpoint(FunctionalTest):
3138 3523
             status=400,
3139 3524
         )
3140 3525
         # Check After
3526
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3527
+        uapi = UserApi(
3528
+            current_user=admin,
3529
+            session=dbsession,
3530
+            config=self.app_config,
3531
+        )
3141 3532
         user = uapi.get_one(user_id)
3142 3533
         assert user.validate_password('pass')
3143 3534
         assert not user.validate_password('mynewpassword')
@@ -3195,6 +3586,12 @@ class TestSetPasswordEndpoint(FunctionalTest):
3195 3586
             status=204,
3196 3587
         )
3197 3588
         # Check After
3589
+        dbsession = get_tm_session(self.session_factory, transaction.manager)
3590
+        uapi = UserApi(
3591
+            current_user=admin,
3592
+            session=dbsession,
3593
+            config=self.app_config,
3594
+        )
3198 3595
         user = uapi.get_one(user_id)
3199 3596
         assert not user.validate_password('pass')
3200 3597
         assert user.validate_password('mynewpassword')

+ 98 - 27
backend/tracim_backend/tests/functional/test_workspaces.py Voir le fichier

@@ -2,7 +2,7 @@
2 2
 """
3 3
 Tests for /api/v2/workspaces subpath endpoints.
4 4
 """
5
-
5
+import requests
6 6
 import transaction
7 7
 from depot.io.utils import FileIntent
8 8
 
@@ -41,8 +41,10 @@ class TestWorkspaceEndpoint(FunctionalTest):
41 41
         assert workspace['slug'] == 'business'
42 42
         assert workspace['label'] == 'Business'
43 43
         assert workspace['description'] == 'All importants documents'
44
-        assert len(workspace['sidebar_entries']) == 7
44
+        assert len(workspace['sidebar_entries']) == 5
45 45
 
46
+        # TODO - G.M - 2018-08-02 - Better test for sidebar entry, make it
47
+        # not fixed on active application/content-file
46 48
         sidebar_entry = workspace['sidebar_entries'][0]
47 49
         assert sidebar_entry['slug'] == 'dashboard'
48 50
         assert sidebar_entry['label'] == 'Dashboard'
@@ -65,33 +67,19 @@ class TestWorkspaceEndpoint(FunctionalTest):
65 67
         assert sidebar_entry['fa_icon'] == "file-text-o"
66 68
 
67 69
         sidebar_entry = workspace['sidebar_entries'][3]
68
-        assert sidebar_entry['slug'] == 'contents/markdownpluspage'
69
-        assert sidebar_entry['label'] == 'Markdown Plus Documents'
70
-        assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=markdownpluspage"    # nopep8
71
-        assert sidebar_entry['hexcolor'] == "#f12d2d"
72
-        assert sidebar_entry['fa_icon'] == "file-code-o"
73
-
74
-        sidebar_entry = workspace['sidebar_entries'][4]
75 70
         assert sidebar_entry['slug'] == 'contents/file'
76 71
         assert sidebar_entry['label'] == 'Files'
77 72
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
78 73
         assert sidebar_entry['hexcolor'] == "#FF9900"
79 74
         assert sidebar_entry['fa_icon'] == "paperclip"
80 75
 
81
-        sidebar_entry = workspace['sidebar_entries'][5]
76
+        sidebar_entry = workspace['sidebar_entries'][4]
82 77
         assert sidebar_entry['slug'] == 'contents/thread'
83 78
         assert sidebar_entry['label'] == 'Threads'
84 79
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
85 80
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
86 81
         assert sidebar_entry['fa_icon'] == "comments-o"
87 82
 
88
-        sidebar_entry = workspace['sidebar_entries'][6]
89
-        assert sidebar_entry['slug'] == 'calendar'
90
-        assert sidebar_entry['label'] == 'Calendar'
91
-        assert sidebar_entry['route'] == "/#/workspaces/1/calendar"  # nopep8
92
-        assert sidebar_entry['hexcolor'] == "#757575"
93
-        assert sidebar_entry['fa_icon'] == "calendar"
94
-
95 83
     def test_api__update_workspace__ok_200__nominal_case(self) -> None:
96 84
         """
97 85
         Test update workspace
@@ -118,7 +106,7 @@ class TestWorkspaceEndpoint(FunctionalTest):
118 106
         assert workspace['slug'] == 'business'
119 107
         assert workspace['label'] == 'Business'
120 108
         assert workspace['description'] == 'All importants documents'
121
-        assert len(workspace['sidebar_entries']) == 7
109
+        assert len(workspace['sidebar_entries']) == 5
122 110
 
123 111
         # modify workspace
124 112
         res = self.testapp.put_json(
@@ -132,7 +120,7 @@ class TestWorkspaceEndpoint(FunctionalTest):
132 120
         assert workspace['slug'] == 'superworkspace'
133 121
         assert workspace['label'] == 'superworkspace'
134 122
         assert workspace['description'] == 'mysuperdescription'
135
-        assert len(workspace['sidebar_entries']) == 7
123
+        assert len(workspace['sidebar_entries']) == 5
136 124
 
137 125
         # after
138 126
         res = self.testapp.get(
@@ -145,7 +133,7 @@ class TestWorkspaceEndpoint(FunctionalTest):
145 133
         assert workspace['slug'] == 'superworkspace'
146 134
         assert workspace['label'] == 'superworkspace'
147 135
         assert workspace['description'] == 'mysuperdescription'
148
-        assert len(workspace['sidebar_entries']) == 7
136
+        assert len(workspace['sidebar_entries']) == 5
149 137
 
150 138
     def test_api__update_workspace__err_400__empty_label(self) -> None:
151 139
         """
@@ -612,6 +600,89 @@ class TestWorkspaceMembersEndpoint(FunctionalTest):
612 600
         assert user_role['workspace_id'] == 1
613 601
 
614 602
 
603
+class TestUserInvitationWithMailActivatedSync(FunctionalTest):
604
+
605
+    fixtures = [BaseFixture, ContentFixtures]
606
+    config_section = 'functional_test_with_mail_test_sync'
607
+
608
+    def test_api__create_workspace_member_role__ok_200__new_user(self):  # nopep8
609
+        """
610
+        Create workspace member role
611
+        :return:
612
+        """
613
+        requests.delete('http://127.0.0.1:8025/api/v1/messages')
614
+        self.testapp.authorization = (
615
+            'Basic',
616
+            (
617
+                'admin@admin.admin',
618
+                'admin@admin.admin'
619
+            )
620
+        )
621
+        # create workspace role
622
+        params = {
623
+            'user_id': None,
624
+            'user_email_or_public_name': 'bob@bob.bob',
625
+            'role': 'content-manager',
626
+        }
627
+        res = self.testapp.post_json(
628
+            '/api/v2/workspaces/1/members',
629
+            status=200,
630
+            params=params,
631
+        )
632
+        user_role_found = res.json_body
633
+        assert user_role_found['role'] == 'content-manager'
634
+        assert user_role_found['user_id']
635
+        user_id = user_role_found['user_id']
636
+        assert user_role_found['workspace_id'] == 1
637
+        assert user_role_found['newly_created'] is True
638
+        assert user_role_found['email_sent'] is True
639
+
640
+        # check mail received
641
+        response = requests.get('http://127.0.0.1:8025/api/v1/messages')
642
+        response = response.json()
643
+        assert len(response) == 1
644
+        headers = response[0]['Content']['Headers']
645
+        assert headers['From'][0] == 'Tracim Notifications <test_user_from+0@localhost>'  # nopep8
646
+        assert headers['To'][0] == 'bob <bob@bob.bob>'
647
+        assert headers['Subject'][0] == '[TRACIM] Created account'
648
+
649
+        # TODO - G.M - 2018-08-02 - Place cleanup outside of the test
650
+        requests.delete('http://127.0.0.1:8025/api/v1/messages')
651
+
652
+
653
+class TestUserInvitationWithMailActivatedASync(FunctionalTest):
654
+
655
+    fixtures = [BaseFixture, ContentFixtures]
656
+    config_section = 'functional_test_with_mail_test_async'
657
+
658
+    def test_api__create_workspace_member_role__ok_200__new_user(self):  # nopep8
659
+        """
660
+        Create workspace member role
661
+        :return:
662
+        """
663
+        self.testapp.authorization = (
664
+            'Basic',
665
+            (
666
+                'admin@admin.admin',
667
+                'admin@admin.admin'
668
+            )
669
+        )
670
+        # create workspace role
671
+        params = {
672
+            'user_id': None,
673
+            'user_email_or_public_name': 'bob@bob.bob',
674
+            'role': 'content-manager',
675
+        }
676
+        res = self.testapp.post_json(
677
+            '/api/v2/workspaces/1/members',
678
+            status=200,
679
+            params=params,
680
+        )
681
+        user_role_found = res.json_body
682
+        assert user_role_found['newly_created'] is True
683
+        assert user_role_found['email_sent'] is False
684
+
685
+
615 686
 class TestWorkspaceContents(FunctionalTest):
616 687
     """
617 688
     Tests for /api/v2/workspaces/{workspace_id}/contents endpoint
@@ -1438,7 +1509,7 @@ class TestWorkspaceContents(FunctionalTest):
1438 1509
         params = {
1439 1510
             'parent_id': None,
1440 1511
             'label': 'GenericCreatedContent',
1441
-            'content_type': 'markdownpage',
1512
+            'content_type': 'html-document',
1442 1513
         }
1443 1514
         res = self.testapp.post_json(
1444 1515
             '/api/v2/workspaces/1/contents',
@@ -1449,7 +1520,7 @@ class TestWorkspaceContents(FunctionalTest):
1449 1520
         assert res.json_body
1450 1521
         assert res.json_body['status'] == 'open'
1451 1522
         assert res.json_body['content_id']
1452
-        assert res.json_body['content_type'] == 'markdownpage'
1523
+        assert res.json_body['content_type'] == 'html-document'
1453 1524
         assert res.json_body['is_archived'] is False
1454 1525
         assert res.json_body['is_deleted'] is False
1455 1526
         assert res.json_body['workspace_id'] == 1
@@ -1480,7 +1551,7 @@ class TestWorkspaceContents(FunctionalTest):
1480 1551
         )
1481 1552
         params = {
1482 1553
             'label': 'GenericCreatedContent',
1483
-            'content_type': 'markdownpage',
1554
+            'content_type': 'html-document',
1484 1555
         }
1485 1556
         res = self.testapp.post_json(
1486 1557
             '/api/v2/workspaces/1/contents',
@@ -1491,7 +1562,7 @@ class TestWorkspaceContents(FunctionalTest):
1491 1562
         assert res.json_body
1492 1563
         assert res.json_body['status'] == 'open'
1493 1564
         assert res.json_body['content_id']
1494
-        assert res.json_body['content_type'] == 'markdownpage'
1565
+        assert res.json_body['content_type'] == 'html-document'
1495 1566
         assert res.json_body['is_archived'] is False
1496 1567
         assert res.json_body['is_deleted'] is False
1497 1568
         assert res.json_body['workspace_id'] == 1
@@ -1544,7 +1615,7 @@ class TestWorkspaceContents(FunctionalTest):
1544 1615
         )
1545 1616
         params = {
1546 1617
             'label': 'GenericCreatedContent',
1547
-            'content_type': 'markdownpage',
1618
+            'content_type': 'html-document',
1548 1619
             'parent_id': 10,
1549 1620
         }
1550 1621
         res = self.testapp.post_json(
@@ -1556,7 +1627,7 @@ class TestWorkspaceContents(FunctionalTest):
1556 1627
         assert res.json_body
1557 1628
         assert res.json_body['status'] == 'open'
1558 1629
         assert res.json_body['content_id']
1559
-        assert res.json_body['content_type'] == 'markdownpage'
1630
+        assert res.json_body['content_type'] == 'html-document'
1560 1631
         assert res.json_body['is_archived'] is False
1561 1632
         assert res.json_body['is_deleted'] is False
1562 1633
         assert res.json_body['workspace_id'] == 1
@@ -1587,7 +1658,7 @@ class TestWorkspaceContents(FunctionalTest):
1587 1658
         )
1588 1659
         params = {
1589 1660
             'label': '',
1590
-            'content_type': 'markdownpage',
1661
+            'content_type': 'html-document',
1591 1662
         }
1592 1663
         res = self.testapp.post_json(
1593 1664
             '/api/v2/workspaces/1/contents',

+ 183 - 0
backend/tracim_backend/tests/library/test_user_api.py Voir le fichier

@@ -1,14 +1,19 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 import pytest
3 3
 import transaction
4
+from tracim_backend import models
4 5
 
5 6
 from tracim_backend.exceptions import AuthenticationFailed
7
+from tracim_backend.exceptions import TooShortAutocompleteString
6 8
 from tracim_backend.exceptions import UserDoesNotExist
7 9
 from tracim_backend.exceptions import UserNotActive
8 10
 from tracim_backend.lib.core.group import GroupApi
9 11
 from tracim_backend.lib.core.user import UserApi
12
+from tracim_backend.lib.core.userworkspace import RoleApi
13
+from tracim_backend.lib.core.workspace import WorkspaceApi
10 14
 from tracim_backend.models import User
11 15
 from tracim_backend.models.context_models import UserInContext
16
+from tracim_backend.models.data import UserRoleInWorkspace
12 17
 from tracim_backend.tests import DefaultTest
13 18
 from tracim_backend.tests import eq_
14 19
 
@@ -107,6 +112,184 @@ class TestUserApi(DefaultTest):
107 112
         # u1 + Admin user from BaseFixture
108 113
         assert 2 == len(users)
109 114
 
115
+    def test_unit__get_known__user__admin__too_short_acp_str(self):
116
+        api = UserApi(
117
+            current_user=None,
118
+            session=self.session,
119
+            config=self.config,
120
+        )
121
+        u1 = api.create_user(
122
+            email='email@email',
123
+            name='name',
124
+            do_notify=False,
125
+            do_save=True,
126
+        )
127
+        with pytest.raises(TooShortAutocompleteString):
128
+            api.get_known_user('e')
129
+
130
+    def test_unit__get_known__user__admin__by_email(self):
131
+        api = UserApi(
132
+            current_user=None,
133
+            session=self.session,
134
+            config=self.config,
135
+        )
136
+        u1 = api.create_user(
137
+            email='email@email',
138
+            name='name',
139
+            do_notify=False,
140
+            do_save=True,
141
+        )
142
+
143
+        users = api.get_known_user('email')
144
+        assert len(users) == 1
145
+        assert users[0] == u1
146
+
147
+    def test_unit__get_known__user__user__no_workspace_empty_known_user(self):
148
+        admin = self.session.query(models.User) \
149
+            .filter(models.User.email == 'admin@admin.admin') \
150
+            .one()
151
+        api = UserApi(
152
+            current_user=admin,
153
+            session=self.session,
154
+            config=self.config,
155
+        )
156
+        u1 = api.create_user(
157
+            email='email@email',
158
+            name='name',
159
+            do_notify=False,
160
+            do_save=True,
161
+        )
162
+        api2 = UserApi(
163
+            current_user=u1,
164
+            session=self.session,
165
+            config=self.config,
166
+        )
167
+        users = api2.get_known_user('email')
168
+        assert len(users) == 0
169
+
170
+    def test_unit__get_known__user__same_workspaces_users_by_name(self):
171
+        admin = self.session.query(models.User) \
172
+            .filter(models.User.email == 'admin@admin.admin') \
173
+            .one()
174
+        api = UserApi(
175
+            current_user=None,
176
+            session=self.session,
177
+            config=self.config,
178
+        )
179
+        u1 = api.create_user(
180
+            email='email@email',
181
+            name='name',
182
+            do_notify=False,
183
+            do_save=True,
184
+        )
185
+        u2 = api.create_user(
186
+            email='email2@email2',
187
+            name='name2',
188
+            do_notify=False,
189
+            do_save=True,
190
+        )
191
+        u3 = api.create_user(
192
+            email='notfound@notfound',
193
+            name='notfound',
194
+            do_notify=False,
195
+            do_save=True,
196
+        )
197
+        wapi = WorkspaceApi(
198
+            current_user=admin,
199
+            session=self.session,
200
+            config=self.app_config,
201
+        )
202
+        workspace = wapi.create_workspace(
203
+            'test workspace n°1',
204
+            save_now=True)
205
+        role_api = RoleApi(
206
+            current_user=admin,
207
+            session=self.session,
208
+            config=self.app_config,
209
+        )
210
+        role_api.create_one(u1, workspace, UserRoleInWorkspace.READER, False)
211
+        role_api.create_one(u2, workspace, UserRoleInWorkspace.READER, False)
212
+        role_api.create_one(u3, workspace, UserRoleInWorkspace.READER, False)
213
+        api2 = UserApi(
214
+            current_user=u1,
215
+            session=self.session,
216
+            config=self.config,
217
+        )
218
+        users = api2.get_known_user('name')
219
+        assert len(users) == 2
220
+        assert users[0] == u1
221
+        assert users[1] == u2
222
+
223
+    def test_unit__get_known__user__same_workspaces_users_by_email(self):
224
+        admin = self.session.query(models.User) \
225
+            .filter(models.User.email == 'admin@admin.admin') \
226
+            .one()
227
+        api = UserApi(
228
+            current_user=None,
229
+            session=self.session,
230
+            config=self.config,
231
+        )
232
+        u1 = api.create_user(
233
+            email='email@email',
234
+            name='name',
235
+            do_notify=False,
236
+            do_save=True,
237
+        )
238
+        u2 = api.create_user(
239
+            email='email2@email2',
240
+            name='name2',
241
+            do_notify=False,
242
+            do_save=True,
243
+        )
244
+        u3 = api.create_user(
245
+            email='notfound@notfound',
246
+            name='notfound',
247
+            do_notify=False,
248
+            do_save=True,
249
+        )
250
+        wapi = WorkspaceApi(
251
+            current_user=admin,
252
+            session=self.session,
253
+            config=self.app_config,
254
+        )
255
+        workspace = wapi.create_workspace(
256
+            'test workspace n°1',
257
+            save_now=True)
258
+        role_api = RoleApi(
259
+            current_user=admin,
260
+            session=self.session,
261
+            config=self.app_config,
262
+        )
263
+        role_api.create_one(u1, workspace, UserRoleInWorkspace.READER, False)
264
+        role_api.create_one(u2, workspace, UserRoleInWorkspace.READER, False)
265
+        role_api.create_one(u3, workspace, UserRoleInWorkspace.READER, False)
266
+        api2 = UserApi(
267
+            current_user=u1,
268
+            session=self.session,
269
+            config=self.config,
270
+        )
271
+        users = api2.get_known_user('email')
272
+        assert len(users) == 2
273
+        assert users[0] == u1
274
+        assert users[1] == u2
275
+
276
+    def test_unit__get_known__user__admin__by_name(self):
277
+        api = UserApi(
278
+            current_user=None,
279
+            session=self.session,
280
+            config=self.config,
281
+        )
282
+        u1 = api.create_user(
283
+            email='email@email',
284
+            name='name',
285
+            do_notify=False,
286
+            do_save=True,
287
+        )
288
+
289
+        users = api.get_known_user('nam')
290
+        assert len(users) == 1
291
+        assert users[0] == u1
292
+
110 293
     def test_unit__get_one__ok__nominal_case(self):
111 294
         api = UserApi(
112 295
             current_user=None,

+ 14 - 0
backend/tracim_backend/tests/library/tests_utils.py Voir le fichier

@@ -0,0 +1,14 @@
1
+import string
2
+
3
+from tracim_backend.lib.utils.utils import password_generator
4
+from tracim_backend.lib.utils.utils import ALLOWED_AUTOGEN_PASSWORD_CHAR
5
+from tracim_backend.lib.utils.utils import DEFAULT_PASSWORD_GEN_CHAR_LENGTH
6
+
7
+
8
+class TestPasswordGenerator(object):
9
+
10
+    def test_password_generator_ok_nominal_case(self):
11
+        password = password_generator()
12
+        assert len(password) == DEFAULT_PASSWORD_GEN_CHAR_LENGTH
13
+        for char in password:
14
+            assert char in ALLOWED_AUTOGEN_PASSWORD_CHAR

+ 6 - 0
backend/tracim_backend/views/contents_api/comment_controller.py Voir le fichier

@@ -41,6 +41,8 @@ class CommentController(Controller):
41 41
         # login = hapic_data.body
42 42
         app_config = request.registry.settings['CFG']
43 43
         api = ContentApi(
44
+            show_archived=True,
45
+            show_deleted=True,
44 46
             current_user=request.current_user,
45 47
             session=request.dbsession,
46 48
             config=app_config,
@@ -68,6 +70,8 @@ class CommentController(Controller):
68 70
         # login = hapic_data.body
69 71
         app_config = request.registry.settings['CFG']
70 72
         api = ContentApi(
73
+            show_archived=True,
74
+            show_deleted=True,
71 75
             current_user=request.current_user,
72 76
             session=request.dbsession,
73 77
             config=app_config,
@@ -97,6 +101,8 @@ class CommentController(Controller):
97 101
         """
98 102
         app_config = request.registry.settings['CFG']
99 103
         api = ContentApi(
104
+            show_archived=True,
105
+            show_deleted=True,
100 106
             current_user=request.current_user,
101 107
             session=request.dbsession,
102 108
             config=app_config,

+ 28 - 0
backend/tracim_backend/views/contents_api/file_controller.py Voir le fichier

@@ -61,6 +61,8 @@ class FileController(Controller):
61 61
         """
62 62
         app_config = request.registry.settings['CFG']
63 63
         api = ContentApi(
64
+            show_archived=True,
65
+            show_deleted=True,
64 66
             current_user=request.current_user,
65 67
             session=request.dbsession,
66 68
             config=app_config,
@@ -95,6 +97,8 @@ class FileController(Controller):
95 97
         """
96 98
         app_config = request.registry.settings['CFG']
97 99
         api = ContentApi(
100
+            show_archived=True,
101
+            show_deleted=True,
98 102
             current_user=request.current_user,
99 103
             session=request.dbsession,
100 104
             config=app_config,
@@ -120,6 +124,8 @@ class FileController(Controller):
120 124
         """
121 125
         app_config = request.registry.settings['CFG']
122 126
         api = ContentApi(
127
+            show_archived=True,
128
+            show_deleted=True,
123 129
             current_user=request.current_user,
124 130
             session=request.dbsession,
125 131
             config=app_config,
@@ -154,6 +160,8 @@ class FileController(Controller):
154 160
         """
155 161
         app_config = request.registry.settings['CFG']
156 162
         api = ContentApi(
163
+            show_archived=True,
164
+            show_deleted=True,
157 165
             current_user=request.current_user,
158 166
             session=request.dbsession,
159 167
             config=app_config,
@@ -181,6 +189,8 @@ class FileController(Controller):
181 189
         """
182 190
         app_config = request.registry.settings['CFG']
183 191
         api = ContentApi(
192
+            show_archived=True,
193
+            show_deleted=True,
184 194
             current_user=request.current_user,
185 195
             session=request.dbsession,
186 196
             config=app_config,
@@ -205,6 +215,8 @@ class FileController(Controller):
205 215
         """
206 216
         app_config = request.registry.settings['CFG']
207 217
         api = ContentApi(
218
+            show_archived=True,
219
+            show_deleted=True,
208 220
             current_user=request.current_user,
209 221
             session=request.dbsession,
210 222
             config=app_config,
@@ -238,6 +250,8 @@ class FileController(Controller):
238 250
         """
239 251
         app_config = request.registry.settings['CFG']
240 252
         api = ContentApi(
253
+            show_archived=True,
254
+            show_deleted=True,
241 255
             current_user=request.current_user,
242 256
             session=request.dbsession,
243 257
             config=app_config,
@@ -270,6 +284,8 @@ class FileController(Controller):
270 284
         """
271 285
         app_config = request.registry.settings['CFG']
272 286
         api = ContentApi(
287
+            show_archived=True,
288
+            show_deleted=True,
273 289
             current_user=request.current_user,
274 290
             session=request.dbsession,
275 291
             config=app_config,
@@ -301,6 +317,8 @@ class FileController(Controller):
301 317
         """
302 318
         app_config = request.registry.settings['CFG']
303 319
         api = ContentApi(
320
+            show_archived=True,
321
+            show_deleted=True,
304 322
             current_user=request.current_user,
305 323
             session=request.dbsession,
306 324
             config=app_config,
@@ -334,6 +352,8 @@ class FileController(Controller):
334 352
         """
335 353
         app_config = request.registry.settings['CFG']
336 354
         api = ContentApi(
355
+            show_archived=True,
356
+            show_deleted=True,
337 357
             current_user=request.current_user,
338 358
             session=request.dbsession,
339 359
             config=app_config,
@@ -352,6 +372,8 @@ class FileController(Controller):
352 372
         """
353 373
         app_config = request.registry.settings['CFG']
354 374
         api = ContentApi(
375
+            show_archived=True,
376
+            show_deleted=True,
355 377
             current_user=request.current_user,
356 378
             session=request.dbsession,
357 379
             config=app_config,
@@ -375,6 +397,8 @@ class FileController(Controller):
375 397
         """
376 398
         app_config = request.registry.settings['CFG']
377 399
         api = ContentApi(
400
+            show_archived=True,
401
+            show_deleted=True,
378 402
             current_user=request.current_user,
379 403
             session=request.dbsession,
380 404
             config=app_config,
@@ -413,6 +437,8 @@ class FileController(Controller):
413 437
         """
414 438
         app_config = request.registry.settings['CFG']
415 439
         api = ContentApi(
440
+            show_archived=True,
441
+            show_deleted=True,
416 442
             current_user=request.current_user,
417 443
             session=request.dbsession,
418 444
             config=app_config,
@@ -440,6 +466,8 @@ class FileController(Controller):
440 466
         """
441 467
         app_config = request.registry.settings['CFG']
442 468
         api = ContentApi(
469
+            show_archived=True,
470
+            show_deleted=True,
443 471
             current_user=request.current_user,
444 472
             session=request.dbsession,
445 473
             config=app_config,

+ 8 - 0
backend/tracim_backend/views/contents_api/html_document_controller.py Voir le fichier

@@ -46,6 +46,8 @@ class HTMLDocumentController(Controller):
46 46
         """
47 47
         app_config = request.registry.settings['CFG']
48 48
         api = ContentApi(
49
+            show_archived=True,
50
+            show_deleted=True,
49 51
             current_user=request.current_user,
50 52
             session=request.dbsession,
51 53
             config=app_config,
@@ -69,6 +71,8 @@ class HTMLDocumentController(Controller):
69 71
         """
70 72
         app_config = request.registry.settings['CFG']
71 73
         api = ContentApi(
74
+            show_archived=True,
75
+            show_deleted=True,
72 76
             current_user=request.current_user,
73 77
             session=request.dbsession,
74 78
             config=app_config,
@@ -107,6 +111,8 @@ class HTMLDocumentController(Controller):
107 111
         """
108 112
         app_config = request.registry.settings['CFG']
109 113
         api = ContentApi(
114
+            show_archived=True,
115
+            show_deleted=True,
110 116
             current_user=request.current_user,
111 117
             session=request.dbsession,
112 118
             config=app_config,
@@ -138,6 +144,8 @@ class HTMLDocumentController(Controller):
138 144
         """
139 145
         app_config = request.registry.settings['CFG']
140 146
         api = ContentApi(
147
+            show_archived=True,
148
+            show_deleted=True,
141 149
             current_user=request.current_user,
142 150
             session=request.dbsession,
143 151
             config=app_config,

+ 8 - 0
backend/tracim_backend/views/contents_api/threads_controller.py Voir le fichier

@@ -45,6 +45,8 @@ class ThreadController(Controller):
45 45
         """
46 46
         app_config = request.registry.settings['CFG']
47 47
         api = ContentApi(
48
+            show_archived=True,
49
+            show_deleted=True,
48 50
             current_user=request.current_user,
49 51
             session=request.dbsession,
50 52
             config=app_config,
@@ -68,6 +70,8 @@ class ThreadController(Controller):
68 70
         """
69 71
         app_config = request.registry.settings['CFG']
70 72
         api = ContentApi(
73
+            show_archived=True,
74
+            show_deleted=True,
71 75
             current_user=request.current_user,
72 76
             session=request.dbsession,
73 77
             config=app_config,
@@ -106,6 +110,8 @@ class ThreadController(Controller):
106 110
         """
107 111
         app_config = request.registry.settings['CFG']
108 112
         api = ContentApi(
113
+            show_archived=True,
114
+            show_deleted=True,
109 115
             current_user=request.current_user,
110 116
             session=request.dbsession,
111 117
             config=app_config,
@@ -132,6 +138,8 @@ class ThreadController(Controller):
132 138
         """
133 139
         app_config = request.registry.settings['CFG']
134 140
         api = ContentApi(
141
+            show_archived=True,
142
+            show_deleted=True,
135 143
             current_user=request.current_user,
136 144
             session=request.dbsession,
137 145
             config=app_config,

+ 13 - 0
backend/tracim_backend/views/core_api/schemas.py Voir le fichier

@@ -2,6 +2,7 @@
2 2
 import marshmallow
3 3
 from marshmallow import post_load
4 4
 from marshmallow.validate import OneOf
5
+from marshmallow.validate import Length
5 6
 from marshmallow.validate import Range
6 7
 
7 8
 from tracim_backend.lib.utils.utils import DATETIME_FORMAT
@@ -11,6 +12,7 @@ from tracim_backend.models.contents import CONTENT_STATUS
11 12
 from tracim_backend.models.contents import CONTENT_TYPES
12 13
 from tracim_backend.models.contents import open_status
13 14
 from tracim_backend.models.context_models import ActiveContentFilter
15
+from tracim_backend.models.context_models import AutocompleteQuery
14 16
 from tracim_backend.models.context_models import ContentIdsQuery
15 17
 from tracim_backend.models.context_models import UserWorkspaceAndContentPath
16 18
 from tracim_backend.models.context_models import ContentCreation
@@ -292,6 +294,17 @@ class CommentsPathSchema(WorkspaceAndContentIdPathSchema):
292 294
         return CommentPath(**data)
293 295
 
294 296
 
297
+class AutocompleteQuerySchema(marshmallow.Schema):
298
+    acp = marshmallow.fields.Str(
299
+        example='test',
300
+        description='search text to query',
301
+        validate=Length(min=2),
302
+    )
303
+    @post_load
304
+    def make_autocomplete(self, data):
305
+        return AutocompleteQuery(**data)
306
+
307
+
295 308
 class PageQuerySchema(marshmallow.Schema):
296 309
     page = marshmallow.fields.Int(
297 310
         example=2,

+ 57 - 0
backend/tracim_backend/views/core_api/user_controller.py Voir le fichier

@@ -18,6 +18,8 @@ from tracim_backend.lib.utils.authorization import require_profile
18 18
 from tracim_backend.exceptions import WrongUserPassword
19 19
 from tracim_backend.exceptions import PasswordDoNotMatch
20 20
 from tracim_backend.views.core_api.schemas import UserSchema
21
+from tracim_backend.views.core_api.schemas import AutocompleteQuerySchema
22
+from tracim_backend.views.core_api.schemas import UserDigestSchema
21 23
 from tracim_backend.views.core_api.schemas import SetEmailSchema
22 24
 from tracim_backend.views.core_api.schemas import SetPasswordSchema
23 25
 from tracim_backend.views.core_api.schemas import UserInfosSchema
@@ -37,6 +39,7 @@ from tracim_backend.models.contents import CONTENT_TYPES
37 39
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
38 40
 
39 41
 
42
+
40 43
 class UserController(Controller):
41 44
 
42 45
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
@@ -77,6 +80,46 @@ class UserController(Controller):
77 80
         return uapi.get_user_with_context(request.candidate_user)
78 81
 
79 82
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
83
+    @require_profile(Group.TIM_ADMIN)
84
+    @hapic.output_body(UserDigestSchema(many=True))
85
+    def users(self, context, request: TracimRequest, hapic_data=None):
86
+        """
87
+        Get all users
88
+        """
89
+        app_config = request.registry.settings['CFG']
90
+        uapi = UserApi(
91
+            current_user=request.current_user,  # User
92
+            session=request.dbsession,
93
+            config=app_config,
94
+        )
95
+        users = uapi.get_all()
96
+        context_users = [
97
+            uapi.get_user_with_context(user) for user in users
98
+        ]
99
+        return context_users
100
+
101
+    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
102
+    @require_same_user_or_profile(Group.TIM_MANAGER)
103
+    @hapic.input_path(UserIdPathSchema())
104
+    @hapic.input_query(AutocompleteQuerySchema())
105
+    @hapic.output_body(UserDigestSchema(many=True))
106
+    def known_members(self, context, request: TracimRequest, hapic_data=None):
107
+        """
108
+        Get known users list
109
+        """
110
+        app_config = request.registry.settings['CFG']
111
+        uapi = UserApi(
112
+            current_user=request.candidate_user,  # User
113
+            session=request.dbsession,
114
+            config=app_config,
115
+        )
116
+        users = uapi.get_known_user(acp=hapic_data.query.acp)
117
+        context_users = [
118
+            uapi.get_user_with_context(user) for user in users
119
+        ]
120
+        return context_users
121
+
122
+    @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
80 123
     @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
81 124
     @require_same_user_or_profile(Group.TIM_ADMIN)
82 125
     @hapic.input_body(SetEmailSchema())
@@ -329,6 +372,8 @@ class UserController(Controller):
329 372
         """
330 373
         app_config = request.registry.settings['CFG']
331 374
         api = ContentApi(
375
+            show_archived=True,
376
+            show_deleted=True,
332 377
             current_user=request.candidate_user,
333 378
             session=request.dbsession,
334 379
             config=app_config,
@@ -346,6 +391,8 @@ class UserController(Controller):
346 391
         """
347 392
         app_config = request.registry.settings['CFG']
348 393
         api = ContentApi(
394
+            show_archived=True,
395
+            show_deleted=True,
349 396
             current_user=request.candidate_user,
350 397
             session=request.dbsession,
351 398
             config=app_config,
@@ -363,6 +410,8 @@ class UserController(Controller):
363 410
         """
364 411
         app_config = request.registry.settings['CFG']
365 412
         api = ContentApi(
413
+            show_archived=True,
414
+            show_deleted=True,
366 415
             current_user=request.candidate_user,
367 416
             session=request.dbsession,
368 417
             config=app_config,
@@ -384,6 +433,14 @@ class UserController(Controller):
384 433
         configurator.add_route('user', '/users/{user_id}', request_method='GET')  # nopep8
385 434
         configurator.add_view(self.user, route_name='user')
386 435
 
436
+        # users lists
437
+        configurator.add_route('users', '/users', request_method='GET')  # nopep8
438
+        configurator.add_view(self.users, route_name='users')
439
+
440
+        # known members lists
441
+        configurator.add_route('known_members', '/users/{user_id}/known_members', request_method='GET')  # nopep8
442
+        configurator.add_view(self.known_members, route_name='known_members')
443
+
387 444
         # set user email
388 445
         configurator.add_route('set_user_email', '/users/{user_id}/email', request_method='PUT')  # nopep8
389 446
         configurator.add_view(self.set_user_email, route_name='set_user_email')

+ 17 - 2
backend/tracim_backend/views/core_api/workspace_controller.py Voir le fichier

@@ -33,6 +33,7 @@ from tracim_backend.exceptions import ContentNotFound
33 33
 from tracim_backend.exceptions import WorkspacesDoNotMatch
34 34
 from tracim_backend.exceptions import ParentNotFound
35 35
 from tracim_backend.views.controllers import Controller
36
+from tracim_backend.lib.utils.utils import password_generator
36 37
 from tracim_backend.views.core_api.schemas import FilterContentQuerySchema
37 38
 from tracim_backend.views.core_api.schemas import ContentIdPathSchema
38 39
 from tracim_backend.views.core_api.schemas import WorkspaceMemberCreationSchema
@@ -216,12 +217,18 @@ class WorkspaceController(Controller):
216 217
                 # TODO - G.M - 2018-07-05 - [UserCreation] Reenable email
217 218
                 # notification for creation
218 219
                 user = uapi.create_user(
219
-                    hapic_data.body.user_email_or_public_name,
220
-                    do_notify=False
220
+                    email=hapic_data.body.user_email_or_public_name,
221
+                    password= password_generator(),
222
+                    do_notify=True
221 223
                 )  # nopep8
222 224
                 newly_created = True
225
+                if app_config.EMAIL_NOTIFICATION_ACTIVATED and \
226
+                        app_config.EMAIL_NOTIFICATION_PROCESSING_MODE.lower() == 'sync':
227
+                    email_sent = True
228
+
223 229
             except EmailValidationFailed:
224 230
                 raise UserCreationFailed('no valid mail given')
231
+
225 232
         role = rapi.create_one(
226 233
             user=user,
227 234
             workspace=request.current_workspace,
@@ -389,6 +396,8 @@ class WorkspaceController(Controller):
389 396
         move_data = hapic_data.body
390 397
 
391 398
         api = ContentApi(
399
+            show_archived=True,
400
+            show_deleted=True,
392 401
             current_user=request.current_user,
393 402
             session=request.dbsession,
394 403
             config=app_config,
@@ -436,6 +445,8 @@ class WorkspaceController(Controller):
436 445
         app_config = request.registry.settings['CFG']
437 446
         path_data = hapic_data.path
438 447
         api = ContentApi(
448
+            show_archived=True,
449
+            show_deleted=True,
439 450
             current_user=request.current_user,
440 451
             session=request.dbsession,
441 452
             config=app_config,
@@ -472,6 +483,7 @@ class WorkspaceController(Controller):
472 483
             session=request.dbsession,
473 484
             config=app_config,
474 485
             show_deleted=True,
486
+            show_archived=True,
475 487
         )
476 488
         content = api.get_one(
477 489
             path_data.content_id,
@@ -501,6 +513,8 @@ class WorkspaceController(Controller):
501 513
         app_config = request.registry.settings['CFG']
502 514
         path_data = hapic_data.path
503 515
         api = ContentApi(
516
+            show_archived=True,
517
+            show_deleted=True,
504 518
             current_user=request.current_user,
505 519
             session=request.dbsession,
506 520
             config=app_config,
@@ -534,6 +548,7 @@ class WorkspaceController(Controller):
534 548
             session=request.dbsession,
535 549
             config=app_config,
536 550
             show_archived=True,
551
+            show_deleted=True,
537 552
         )
538 553
         content = api.get_one(
539 554
             path_data.content_id,

+ 1 - 1
bash_library.sh Voir le fichier

@@ -5,5 +5,5 @@ BROWN='\033[0;33m'
5 5
 NC='\033[0m' # No Color
6 6
 
7 7
 function log {
8
-    echo -e "\n${YELLOW}[$(date +'%H:%M:%S')]${BROWN} $ $1${NC}\n"
8
+    echo -e "\n${YELLOW}[$(date +'%H:%M:%S')]${BROWN} $ $1${NC}"
9 9
 }

+ 39 - 68
build_full_frontend.sh Voir le fichier

@@ -1,5 +1,6 @@
1 1
 #!/bin/bash
2 2
 
3
+# shellcheck disable=SC1091
3 4
 . bash_library.sh # source bash_library.sh
4 5
 
5 6
 windoz=""
@@ -7,84 +8,54 @@ if  [[ $1 = "-w" ]]; then
7 8
     windoz="windoz"
8 9
 fi
9 10
 
10
-echo -e "\n${BROWN}/!\ ${NC}this script does not run 'npm install'\n${BROWN}/!\ ${NC}it also assumes your webpack dev server of frontend is running"
11
+echo -e "\n${BROWN}/!\ ${NC}this script does not run 'npm install'\n${BROWN}/!\ ${NC}"
11 12
 
12
-# Tracim Lib
13
-
14
-log "cd frontend_lib"
15
-cd frontend_lib
16
-log "npm run buildtracimlib$windoz"
17
-npm run buildtracimlib$windoz
18
-cd -
19
-
20
-# app Html Document
13
+# get the new sources
14
+git pull origin develop
21 15
 
22
-log "cd frontend_app_html-document"
23
-cd frontend_app_html-document
16
+# create folder frontend/dist/app/ if no exists
17
+if [ ! -d "frontend/dist/app/" ]; then
18
+  mkdir frontend/dist/app/
19
+fi
24 20
 
25
-log "npm run build$windoz # for frontend_app_html-document"
26
-npm run build$windoz
21
+# Tracim Lib
22
+(
23
+  log "build frontend_lib"
24
+  cd frontend_lib || exit
25
+  npm run buildtracimlib$windoz
26
+)
27 27
 
28
-log "cp dist/html-document.app.js"
29
-cp dist/html-document.app.js ../frontend/dist/app
30 28
 
31
-log "cp i18next.scanner/en/translation.json ../frontend/dist/app/tml-document_en_translation.json"
32
-cp i18next.scanner/en/translation.json ../frontend/dist/app/html-document_en_translation.json
29
+# app Html Document
30
+(
31
+  cd frontend_app_html-document || exit
32
+  ./build_html-document.sh
33
+)
33 34
 
34
-log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json"
35
-cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json
36
-cd -
37 35
 
38 36
 # app Thread
37
+(
38
+  cd frontend_app_thread || exit
39
+  ./build_thread.sh
40
+)
39 41
 
40
-log "cd frontend_app_thread"
41
-cd frontend_app_thread
42
-
43
-log "npm run build$windoz # for frontend_app_thread"
44
-npm run build$windoz
45
-
46
-log "cp dist/thread.app.js"
47
-cp dist/thread.app.js ../frontend/dist/app
48
-
49
-log "cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json"
50
-cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json
51
-
52
-log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json"
53
-cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json
54
-cd -
55 42
 
56 43
 # app Workspace
57
-
58
-log "cd frontend_app_workspace"
59
-cd frontend_app_workspace
60
-
61
-log "npm run build$windoz # for frontend_app_workspace"
62
-npm run build$windoz
63
-
64
-log "cp dist/workspace.app.js"
65
-cp dist/workspace.app.js ../frontend/dist/app
66
-
67
-log "cp i18next.scanner/en/translation.json ../frontend/dist/app/workspace_en_translation.json"
68
-cp i18next.scanner/en/translation.json ../frontend/dist/app/workspace_en_translation.json
69
-
70
-log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/workspace_fr_translation.json"
71
-cp i18next.scanner/fr/translation.json ../frontend/dist/app/workspace_fr_translation.json
72
-cd -
44
+(
45
+  cd frontend_app_workspace || exit
46
+  ./build_workspace.sh
47
+)
73 48
 
74 49
 # app Admin Workspace User
75
-
76
-log "cd frontend_app_admin_workspace_user"
77
-cd frontend_app_admin_workspace_user
78
-
79
-log "npm run build$windoz # for frontend_app_thread"
80
-npm run build$windoz
81
-
82
-log "cp dist/admin_workspace_user.app.js"
83
-cp dist/admin_workspace_user.app.js ../frontend/dist/app
84
-
85
-log "cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json"
86
-cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json
87
-
88
-log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json"
89
-cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json
90
-cd -
50
+(
51
+  cd frontend_app_admin_workspace_user || exit
52
+  ./build_admin_workspace_user.sh
53
+)
54
+
55
+# build Tracim
56
+(
57
+  cd frontend || exit
58
+  npm run build
59
+)
60
+
61
+log "-- frontend build successful."

+ 6 - 0
color.json.sample Voir le fichier

@@ -0,0 +1,6 @@
1
+{
2
+  "primary": "#7d4e24",
3
+  "html-document": "#3f52e3",
4
+  "thread": "#ad4cf9",
5
+  "file": "#ff9900"
6
+}

+ 3 - 1
frontend/.gitignore Voir le fichier

@@ -1,4 +1,6 @@
1 1
 # Created by .ignore support plugin (hsz.mobi)
2
-.idea/
3 2
 .git/
4 3
 node_modules/
4
+dist/asset/tracim.app.entry.js
5
+dist/asset/tracim.vendor.bundle.js
6
+dist/asset/images/

frontend/dist/dev/bootstrap-4.0.0-beta.2.js → frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.2.js Voir le fichier


frontend/dist/dev/bootstrap-4.0.0-beta.css → frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.css Voir le fichier


frontend/dist/dev/jquery-3.2.1.js → frontend/dist/asset/bootstrap/jquery-3.2.1.js Voir le fichier


frontend/dist/dev/popper-1.12.3.js → frontend/dist/asset/bootstrap/popper-1.12.3.js Voir le fichier


frontend/dist/font/font-awesome-4.7.0/HELP-US-OUT.txt → frontend/dist/asset/font/font-awesome-4.7.0/HELP-US-OUT.txt Voir le fichier


frontend/dist/font/font-awesome-4.7.0/css/font-awesome.css → frontend/dist/asset/font/font-awesome-4.7.0/css/font-awesome.css Voir le fichier


frontend/dist/font/font-awesome-4.7.0/css/font-awesome.min.css → frontend/dist/asset/font/font-awesome-4.7.0/css/font-awesome.min.css Voir le fichier


frontend/dist/font/font-awesome-4.7.0/fonts/FontAwesome.otf → frontend/dist/asset/font/font-awesome-4.7.0/fonts/FontAwesome.otf Voir le fichier


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.eot → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.eot Voir le fichier


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.svg → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.svg Voir le fichier


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf Voir le fichier


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff Voir le fichier


frontend/dist/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 → frontend/dist/asset/font/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/animated.less → frontend/dist/asset/font/font-awesome-4.7.0/less/animated.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/bordered-pulled.less → frontend/dist/asset/font/font-awesome-4.7.0/less/bordered-pulled.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/core.less → frontend/dist/asset/font/font-awesome-4.7.0/less/core.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/fixed-width.less → frontend/dist/asset/font/font-awesome-4.7.0/less/fixed-width.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/font-awesome.less → frontend/dist/asset/font/font-awesome-4.7.0/less/font-awesome.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/icons.less → frontend/dist/asset/font/font-awesome-4.7.0/less/icons.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/larger.less → frontend/dist/asset/font/font-awesome-4.7.0/less/larger.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/list.less → frontend/dist/asset/font/font-awesome-4.7.0/less/list.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/mixins.less → frontend/dist/asset/font/font-awesome-4.7.0/less/mixins.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/path.less → frontend/dist/asset/font/font-awesome-4.7.0/less/path.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/rotated-flipped.less → frontend/dist/asset/font/font-awesome-4.7.0/less/rotated-flipped.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/screen-reader.less → frontend/dist/asset/font/font-awesome-4.7.0/less/screen-reader.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/stacked.less → frontend/dist/asset/font/font-awesome-4.7.0/less/stacked.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/less/variables.less → frontend/dist/asset/font/font-awesome-4.7.0/less/variables.less Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_animated.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_animated.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_bordered-pulled.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_bordered-pulled.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_core.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_core.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_fixed-width.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_fixed-width.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_icons.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_icons.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_larger.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_larger.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_list.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_list.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_mixins.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_mixins.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_path.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_path.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_rotated-flipped.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_rotated-flipped.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_screen-reader.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_screen-reader.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_stacked.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_stacked.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/_variables.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_variables.scss Voir le fichier


frontend/dist/font/font-awesome-4.7.0/scss/font-awesome.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/font-awesome.scss Voir le fichier


frontend/dist/appInterface.js → frontend/dist/asset/tracim/appInterface.js Voir le fichier

@@ -76,6 +76,7 @@
76 76
   GLOBAL_eventReducer = ({detail: {type, data}}) => {
77 77
     switch (type) {
78 78
       case 'hide_popupCreateContent':
79
+      case 'hide_popupCreateWorkspace':
79 80
         console.log('%cGLOBAL_eventReducer Custom Event', 'color: #28a745', type, data)
80 81
         getSelectedApp(data.name).unmountApp('popupCreateContentContainer')
81 82
         break

frontend/dist/tinymceInit.js → frontend/dist/asset/tracim/tinymceInit.js Voir le fichier


BIN
frontend/dist/ecbb61e619a4d2801db1054c019316cc.jpg Voir le fichier


+ 16 - 17
frontend/dist/index.html Voir le fichier

@@ -6,20 +6,19 @@
6 6
     <title>Tracim</title>
7 7
     <link rel='shortcut icon' type='image/x-icon' href='/asset/favicon.ico' >
8 8
 
9
-    <link rel="stylesheet" type="text/css" href="/font/font-awesome-4.7.0/css/font-awesome.css">
9
+    <link rel="stylesheet" type="text/css" href="/asset/font/font-awesome-4.7.0/css/font-awesome.css">
10 10
     <link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
11 11
     <!--
12 12
     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
13 13
     -->
14 14
     <link rel="stylesheet" type="text/css" href="/asset/hamburger/hamburgers.min.css">
15
-    <link rel="stylesheet" type="text/css" href="/dev/bootstrap-4.0.0-beta.css">
15
+    <link rel="stylesheet" type="text/css" href="/asset/bootstrap/bootstrap-4.0.0-beta.css">
16 16
 
17 17
     <style>
18 18
       /* code bellow will be generated by backend */
19 19
       .primaryColorFont { color: #7d4e24; }
20 20
       .primaryColorFontDarken { color: #572800; }
21 21
       .primaryColorFontLighten { color: #a3744a; }
22
-      .whiteFontColor {color: #fdfdfd;}
23 22
 
24 23
       .primaryColorFontHover:hover { color: #7d4e24; }
25 24
       .primaryColorFontDarkenHover:hover { color: #572800; }
@@ -47,23 +46,23 @@
47 46
   <body>
48 47
     <div id='content'></div>
49 48
 
50
-    <script src='/tracim.vendor.bundle.js'></script>
51
-    <script src='/tracim.app.entry.js'></script>
49
+    <script type='text/javascript' src='/asset/tracim.vendor.bundle.js'></script>
50
+    <script type='text/javascript' src='/asset/tracim.app.entry.js'></script>
52 51
 
53
-    <script src='/app/workspace.app.js'></script>
54
-    <script src='/app/html-document.app.js'></script>
55
-    <script src='/app/thread.app.js'></script>
56
-    <!-- <script src='/app/file.app.js'></script> -->
57
-    <script src='/app/admin_workspace_user.app.js'></script>
52
+    <script type='text/javascript' src='/app/workspace.app.js'></script>
53
+    <script type='text/javascript' src='/app/html-document.app.js'></script>
54
+    <script type='text/javascript' src='/app/thread.app.js'></script>
55
+    <!-- <script type='text/javascript' src='/app/file.app.js'></script> -->
56
+    <script type='text/javascript' src='/app/admin_workspace_user.app.js'></script>
58 57
 
59
-    <script src="/dev/jquery-3.2.1.js"></script>
60
-    <script src="/dev/popper-1.12.3.js"></script>
61
-    <script src="/dev/bootstrap-4.0.0-beta.2.js"></script>
58
+    <script type='text/javascript' src='/asset/bootstrap/jquery-3.2.1.js'></script>
59
+    <script type='text/javascript' src='/asset/bootstrap/popper-1.12.3.js'></script>
60
+    <script type='text/javascript' src='/asset/bootstrap/bootstrap-4.0.0-beta.2.js'></script>
62 61
 
63
-    <script type="text/javascript" src="/asset/tinymce/js/tinymce/jquery.tinymce.min.js"></script>
64
-    <script type="text/javascript" src="/asset/tinymce/js/tinymce/tinymce.min.js"></script>
62
+    <script type='text/javascript' src='/asset/tinymce/js/tinymce/jquery.tinymce.min.js'></script>
63
+    <script type='text/javascript' src='/asset/tinymce/js/tinymce/tinymce.min.js'></script>
65 64
 
66
-    <script type='text/javascript' src='/appInterface.js'></script>
67
-    <script type='text/javascript' src='/tinymceInit.js'></script>
65
+    <script type='text/javascript' src='/asset/tracim/appInterface.js'></script>
66
+    <script type='text/javascript' src='/asset/tracim/tinymceInit.js'></script>
68 67
   </body>
69 68
 </html>

+ 5 - 12
frontend/i18next.scanner/en/translation.json Voir le fichier

@@ -37,11 +37,6 @@
37 37
   "Change your status": "Change your status",
38 38
   "subscriber": "subscriber",
39 39
   "unsubscribed": "unsubscribed",
40
-  "Start a new Thread": "Start a new Thread",
41
-  "Writing a document": "Writing a document",
42
-  "Upload a file": "Upload a file",
43
-  "Start a videoconference": "Start a videoconference",
44
-  "View the Calendar": "View the Calendar",
45 40
   "Recent activity": "Recent activity",
46 41
   "Mark everything as read": "Mark everything as read",
47 42
   "See more": "See more",
@@ -50,24 +45,22 @@
50 45
   "Enter the name or email of the member": "Enter the name or email of the member",
51 46
   "Create an account": "Create an account",
52 47
   "Choose the role of the member": "Choose the role of the member",
53
-  "Supervisor": "Supervisor",
54
-  "Content Manager": "Content Manager",
55
-  "Contributor": "Contributor",
56
-  "Reader": "Reader",
57 48
   "Validate": "Validate",
58 49
   "Implement Tracim in your explorer": "Implement Tracim in your explorer",
59 50
   "Find all your documents deposited online directly on your computer via the workstation, without going through the software.": "Find all your documents deposited online directly on your computer via the workstation, without going through the software.",
60 51
   "Workspace Calendar": "Workspace Calendar",
61 52
   "Each workspace has its own calendar.": "Each workspace has its own calendar.",
62 53
   "Email Adress": "Email Adress",
63
-  "Explore the workspace": "Explore the workspace",
64 54
   "Old password": "Old password",
65 55
   "New password": "New password",
66 56
   "Name:": "Name:",
67 57
   "Email Adress:": "Email Adress:",
68
-  "An error has happened": "An error has happened",
69 58
   "You have subscribed to this workspace's notifications": "You have subscribed to this workspace's notifications",
70 59
   "Connection": "Connection",
71 60
   "Forgotten password ?": "Forgotten password ?",
72
-  "currently, you are ": "currently, you are "
61
+  "An error has happened when fetching workspace detail": "An error has happened when fetching workspace detail",
62
+  "An error has happened while fetching member list": "An error has happened while fetching member list",
63
+  "An error has happened while fetching recent activity list": "An error has happened while fetching recent activity list",
64
+  "An error has happened while fetching read status list": "An error has happened while fetching read status list",
65
+  "Hi {{name}} ! Currently, you are ": "Hi {{name}} ! Currently, you are "
73 66
 }

+ 5 - 12
frontend/i18next.scanner/fr/translation.json Voir le fichier

@@ -37,12 +37,6 @@
37 37
   "Change your status": "Changer de status",
38 38
   "subscriber": "Abonné(e)",
39 39
   "unsubscribed": "Non Abonné(e)",
40
-  "Start a new Thread": "Débuter une nouvelle discussion",
41
-  "Writing a document": "Rédiger un document",
42
-  "Upload a file": "Importer un fichier",
43
-  "Start a videoconference": "Débuter une visioconférence",
44
-  "View the Calendar": "Voir le Calendrier",
45
-  "Explore the workspace": "Explorer l'espace de travail",
46 40
   "Recent activity": "Activité récente",
47 41
   "Mark everything as read": "Tout marquer comme lu",
48 42
   "See more": "Voir plus",
@@ -50,10 +44,6 @@
50 44
   "Add a member": "Ajouter un membre",
51 45
   "Enter the name or email of the member": "Renseigner le nom ou l'email de l'utilisateur",
52 46
   "Create an account": "Créer un compte",
53
-  "Supervisor": "Responsable",
54
-  "Content Manager": "Gestionnaire de contenu",
55
-  "Contributor": "Contributeur",
56
-  "Reader": "Lecteur",
57 47
   "Validate": "Validé",
58 48
   "Implement Tracim in your explorer": "Implémenter Tracim dans votre explorateur",
59 49
   "Find all your documents deposited online directly on your computer via the workstation, without going through the software.": "Retrouvez tous vos documents déposés en ligne directement sur votre ordinateur via le poste de travail, sans passer par le logiciel.",
@@ -65,9 +55,12 @@
65 55
   "New password": "Nouveau mot de passe",
66 56
   "Name:": "Nom :",
67 57
   "Email Adress:": "Adresse mail :",
68
-  "An error has happened": "__NOT_TRANSLATED__",
69 58
   "You have subscribed to this workspace's notifications": "Vous êtes abonné aux notifications de cet espace de travail.",
70 59
   "Connection": "Connexion",
71 60
   "Forgotten password ?": "Mot de passe oublié ?",
72
-  "currently, you are ": "actuellement, vous êtes "
61
+  "An error has happened when fetching workspace detail": "__NOT_TRANSLATED__",
62
+  "An error has happened while fetching member list": "__NOT_TRANSLATED__",
63
+  "An error has happened while fetching recent activity list": "__NOT_TRANSLATED__",
64
+  "An error has happened while fetching read status list": "__NOT_TRANSLATED__",
65
+  "Hi {{name}} ! Currently, you are ": "Bonjour {{name}} ! Vous êtes actuellement"
73 66
 }

+ 2 - 5
frontend/src/component/Workspace/OpenContentApp.jsx Voir le fichier

@@ -22,13 +22,10 @@ export class OpenContentApp extends React.Component {
22 22
       console.log('%c<OpenContentApp> contentToOpen', 'color: #dcae84', contentToOpen)
23 23
 
24 24
       if (appOpenedType === contentToOpen.type) { // app already open
25
-        dispatchCustomEvent({
26
-          type: `${contentToOpen.type}_reloadContent`, // handled by html-document:src/container/AdminWorkspaceUser.jsx
27
-          data: contentToOpen
28
-        })
25
+        dispatchCustomEvent(`${contentToOpen.type}_reloadContent`, contentToOpen)
29 26
       } else { // open another app
30 27
         // if another app is already visible, hide it
31
-        if (appOpenedType !== false) dispatchCustomEvent({type: `${appOpenedType}_hideApp`})
28
+        if (appOpenedType !== false) dispatchCustomEvent(`${appOpenedType}_hideApp`, {})
32 29
         // open app
33 30
         renderAppFeature(
34 31
           contentType.find(ct => ct.slug === contentToOpen.type),

+ 1 - 4
frontend/src/container/Account.jsx Voir le fichier

@@ -1,6 +1,5 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3
-import Sidebar from './Sidebar.jsx'
4 3
 import UserInfo from '../component/Account/UserInfo.jsx'
5 4
 import MenuSubComponent from '../component/Account/MenuSubComponent.jsx'
6 5
 import PersonalData from '../component/Account/PersonalData.jsx'
@@ -102,9 +101,7 @@ class Account extends React.Component {
102 101
     })()
103 102
 
104 103
     return (
105
-      <div className='sidebarpagecontainer'>
106
-        <Sidebar />
107
-
104
+      <div className='Account'>
108 105
         <PageWrapper customClass='account'>
109 106
           <PageTitle
110 107
             parentClass={'account'}

+ 138 - 0
frontend/src/container/AdminWorkspacePage.jsx Voir le fichier

@@ -0,0 +1,138 @@
1
+import React from 'react'
2
+import PropTypes from 'prop-types'
3
+import Sidebar from './Sidebar.jsx'
4
+import {
5
+  Delimiter,
6
+  PageWrapper,
7
+  PageTitle,
8
+  PageContent
9
+} from 'tracim_frontend_lib'
10
+import { translate } from 'react-i18next'
11
+
12
+class AdminWorkspacePage extends React.Component {
13
+  render () {
14
+    return (
15
+      <div className='sidebarpagecontainer'>
16
+        <Sidebar />
17
+
18
+        <PageWrapper customClass='adminWorkspacePage'>
19
+          <PageTitle
20
+            parentClass={'adminWorkspacePage'}
21
+            title={'Workspace management'}
22
+          />
23
+
24
+          <PageContent parentClass='adminWorkspacePage'>
25
+
26
+            <div className='adminWorkspacePage__description'>
27
+              This page informs all workspaces of the instances
28
+            </div>
29
+
30
+            <div className='adminWorkspacePage__createworkspace'>
31
+              <button className='adminWorkspacePage__createworkspace__btncreate btn btn-primary primaryColorBg primaryColorBorder primaryColorBorderDarkenHover'>
32
+                {this.props.t('Create a workspace')}
33
+              </button>
34
+            </div>
35
+
36
+            <Delimiter customClass={'adminWorkspacePage__delimiter'} />
37
+
38
+            <div className='adminWorkspacePage__workspaceTable'>
39
+
40
+              <table class='table'>
41
+                <thead>
42
+                  <tr>
43
+                    <th scope='col'># ID</th>
44
+                    <th scope='col'>Workspace</th>
45
+                    <th scope='col'>Description</th>
46
+                    <th scope='col'>Users</th>
47
+                    <th scope='col'>Calendar</th>
48
+                  </tr>
49
+                </thead>
50
+                <tbody>
51
+                  <tr>
52
+                    <th>#1</th>
53
+                    <td>Design v_2</td>
54
+                    <td>Workspace about tracim v2 design</td>
55
+                    <td>8 users</td>
56
+                    <td className='d-flex align-items-center flex-wrap'>
57
+                      <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
58
+                        <i className='fa fa-fw fa-check-square-o' />
59
+                      </div>
60
+                      Enable
61
+                    </td>
62
+                  </tr>
63
+                  <tr>
64
+                    <th>#2</th>
65
+                    <td>New features</td>
66
+                    <td>Add a new features : Annotated files</td>
67
+                    <td>5 users</td>
68
+                    <td className='d-flex align-items-center flex-wrap'>
69
+                      <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
70
+                        <i className='fa fa-fw fa-square-o' />
71
+                      </div>
72
+                      Disable
73
+                    </td>
74
+                  </tr>
75
+                  <tr>
76
+                    <th>#3</th>
77
+                    <td>Fix Backend</td>
78
+                    <td>workspace referring to multiple issues on the backend </td>
79
+                    <td>3 users</td>
80
+                    <td className='d-flex align-items-center flex-wrap'>
81
+                      <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
82
+                        <i className='fa fa-fw fa-check-square-o' />
83
+                      </div>
84
+                      Enable
85
+                    </td>
86
+                  </tr>
87
+                  <tr>
88
+                    <th>#4</th>
89
+                    <td>Design v_2</td>
90
+                    <td>Workspace about tracim v2 design</td>
91
+                    <td>8 users</td>
92
+                    <td className='d-flex align-items-center flex-wrap'>
93
+                      <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
94
+                        <i className='fa fa-fw fa-square-o' />
95
+                      </div>
96
+                      Disable
97
+                    </td>
98
+                  </tr>
99
+                  <tr>
100
+                    <th>#5</th>
101
+                    <td>New features</td>
102
+                    <td>Add a new features : Annotated files</td>
103
+                    <td>5 users</td>
104
+                    <td className='d-flex align-items-center flex-wrap'>
105
+                      <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
106
+                        <i className='fa fa-fw fa-square-o' />
107
+                      </div>
108
+                      Disable
109
+                    </td>
110
+                  </tr>
111
+                  <tr>
112
+                    <th>#6</th>
113
+                    <td>Fix Backend</td>
114
+                    <td>workspace referring to multiple issues on the backend </td>
115
+                    <td>3 users</td>
116
+                    <td className='d-flex align-items-center flex-wrap'>
117
+                      <div className='adminWorkspacePage__workspaceTable__calendaricon mr-2'>
118
+                        <i className='fa fa-fw fa-check-square-o' />
119
+                      </div>
120
+                      Enable
121
+                    </td>
122
+                  </tr>
123
+                </tbody>
124
+              </table>
125
+            </div>
126
+
127
+          </PageContent>
128
+        </PageWrapper>
129
+      </div>
130
+    )
131
+  }
132
+}
133
+
134
+AdminWorkspacePage.propTypes = {
135
+  availableApp: PropTypes.array.isRequired
136
+}
137
+
138
+export default translate()(AdminWorkspacePage)

+ 4 - 7
frontend/src/container/AppFullscreenManager.jsx Voir le fichier

@@ -4,28 +4,25 @@ import { withRouter } from 'react-router'
4 4
 import { Route } from 'react-router-dom'
5 5
 import { PAGE } from '../helper.js'
6 6
 import appFactory from '../appFactory.js'
7
-import Sidebar from './Sidebar.jsx'
8 7
 
9 8
 class AppFullscreenManager extends React.Component {
10 9
   constructor (props) {
11 10
     super(props)
12 11
     this.state = {
13
-      AmIMounted: false
12
+      isMounted: false
14 13
     }
15 14
   }
16 15
 
17
-  componentDidMount = () => this.setState({AmIMounted: true})
16
+  componentDidMount = () => this.setState({isMounted: true})
18 17
 
19 18
   render () {
20 19
     const { props } = this
21 20
 
22 21
     return (
23
-      <div className='sidebarpagecontainer'>
24
-        <Sidebar />
25
-
22
+      <div className='AppFullScreenManager'>
26 23
         <div id='appFullscreenContainer' />
27 24
 
28
-        {this.state.AmIMounted && (// we must wait for the component to be fully mounted to be sure the div#appFullscreenContainer exists in DOM
25
+        {this.state.isMounted && (// we must wait for the component to be fully mounted to be sure the div#appFullscreenContainer exists in DOM
29 26
           <div className='emptyDiForRoute'>
30 27
             <Route path={PAGE.ADMIN.WORKSPACE} render={() => {
31 28
               props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'workspace'}, props.user, {})

+ 3 - 6
frontend/src/container/Dashboard.jsx Voir le fichier

@@ -1,6 +1,5 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3
-import Sidebar from './Sidebar.jsx'
4 3
 import { translate } from 'react-i18next'
5 4
 import {
6 5
   PageWrapper,
@@ -45,7 +44,7 @@ class Dashboard extends React.Component {
45 44
       case 200:
46 45
         props.dispatch(setWorkspaceDetail(fetchWorkspaceDetail.json)); break
47 46
       default:
48
-        props.dispatch(newFlashMessage(props.t('An error has happened when fetching workspace detail'), 'warning')); break
47
+        props.dispatch(newFlashMessage(props.t('An error has happened while fetching workspace detail'), 'warning')); break
49 48
     }
50 49
 
51 50
     const fetchWorkspaceMemberList = await props.dispatch(getWorkspaceMemberList(props.user, state.workspaceIdInUrl))
@@ -93,9 +92,7 @@ class Dashboard extends React.Component {
93 92
     const { props, state } = this
94 93
 
95 94
     return (
96
-      <div className='sidebarpagecontainer'>
97
-        <Sidebar />
98
-
95
+      <div className='Dashboard' style={{width: '100%'}}>
99 96
         <PageWrapper customeClass='dashboard'>
100 97
           <PageTitle
101 98
             parentClass='dashboard__header'
@@ -124,7 +121,7 @@ class Dashboard extends React.Component {
124 121
               <div className='dashboard__userstatut'>
125 122
                 <div className='dashboard__userstatut__role'>
126 123
                   <div className='dashboard__userstatut__role__msg'>
127
-                    {props.t(`Hi ! ${props.user.public_name} `)}{props.t('currently, you are ')}
124
+                    {props.t('Hi {{name}} ! Currently, you are ', {name: props.user.public_name})}
128 125
                   </div>
129 126
 
130 127
                   {(() => {

+ 4 - 4
frontend/src/container/Login.jsx Voir le fichier

@@ -55,8 +55,8 @@ class Login extends React.Component {
55 55
       Cookies.set(COOKIE.USER_LOGIN, inputLogin.value)
56 56
       Cookies.set(COOKIE.USER_AUTH, userAuth)
57 57
 
58
-      history.push(PAGE.HOME)
59
-    } else if (fetchPostUserLogin.status === 400) {
58
+      history.push(PAGE.WORKSPACE.ROOT)
59
+    } else if (fetchPostUserLogin.status === 403) {
60 60
       dispatch(newFlashMessage(t('Email or password invalid'), 'danger'))
61 61
     }
62 62
   }
@@ -140,8 +140,8 @@ class Login extends React.Component {
140 140
           </div>
141 141
 
142 142
           <footer className='loginpage__footer'>
143
-            <div className='loginpage__footer__text whiteFontColor'>
144
-              copyright © 2013 - 2018 tracim project.
143
+            <div className='loginpage__footer__text d-flex align-items-center flex wrap'>
144
+            copyright © 2013 - 2018 <a href='http://www.tracim.fr/' target='_blank' className='ml-3'>tracim.fr</a>
145 145
             </div>
146 146
           </footer>
147 147
 

+ 7 - 4
frontend/src/container/Sidebar.jsx Voir le fichier

@@ -25,7 +25,7 @@ class Sidebar extends React.Component {
25 25
     super(props)
26 26
     this.state = {
27 27
       sidebarClose: false,
28
-      workspaceIdInUrl: props.match.params.idws ? parseInt(props.match.params.idws) : null
28
+      workspaceIdInUrl: props.match && props.match.params.idws ? parseInt(props.match.params.idws) : null
29 29
     }
30 30
 
31 31
     document.addEventListener('appCustomEvent', this.customEventReducer)
@@ -41,14 +41,17 @@ class Sidebar extends React.Component {
41 41
   }
42 42
 
43 43
   componentDidMount () {
44
+    // console.log('Sidebar Did Mount', this.props)
44 45
     this.loadAppConfigAndWorkspaceList()
45 46
   }
46 47
 
47 48
   componentDidUpdate (prevProps, prevState) {
49
+    const { props } = this
50
+
48 51
     // console.log('%c<Sidebar> Did Update', 'color: #c17838')
49
-    if (this.props.match.params.idws === undefined || isNaN(this.props.match.params.idws)) return
52
+    if (!props.match || props.match.params.idws === undefined || isNaN(props.match.params.idws)) return
50 53
 
51
-    const newWorkspaceId = parseInt(this.props.match.params.idws)
54
+    const newWorkspaceId = parseInt(props.match.params.idws)
52 55
     if (prevState.workspaceIdInUrl !== newWorkspaceId) this.setState({workspaceIdInUrl: newWorkspaceId})
53 56
   }
54 57
 
@@ -104,7 +107,7 @@ class Sidebar extends React.Component {
104 107
     return (
105 108
       <div className={classnames('sidebar primaryColorBgDarken', {'sidebarclose': sidebarClose})}>
106 109
         <div className='sidebarSticky'>
107
-          <div className='sidebar__expand primaryColorBg whiteColorBorder' onClick={this.handleClickToggleSidebar}>
110
+          <div className='sidebar__expand primaryColorBg' onClick={this.handleClickToggleSidebar}>
108 111
             <i className={classnames('fa fa-chevron-left', {'fa-chevron-right': sidebarClose, 'fa-chevron-left': !sidebarClose})} />
109 112
           </div>
110 113
 

+ 62 - 17
frontend/src/container/Tracim.jsx Voir le fichier

@@ -1,18 +1,18 @@
1 1
 import React from 'react'
2 2
 import { connect } from 'react-redux'
3 3
 import { translate } from 'react-i18next'
4
+import Sidebar from './Sidebar.jsx'
4 5
 import Header from './Header.jsx'
5 6
 import Login from './Login.jsx'
6
-import Dashboard from './Dashboard.jsx'
7 7
 import Account from './Account.jsx'
8
+import AdminWorkspacePage from './AdminWorkspacePage.jsx'
8 9
 import AppFullscreenManager from './AppFullscreenManager.jsx'
9 10
 import FlashMessage from '../component/FlashMessage.jsx'
10 11
 import WorkspaceContent from './WorkspaceContent.jsx'
11 12
 import WIPcomponent from './WIPcomponent.jsx'
12 13
 import {
13
-  Route, withRouter, Switch
14
+  Route, withRouter, Redirect
14 15
 } from 'react-router-dom'
15
-import PrivateRoute from './PrivateRoute.jsx'
16 16
 import { COOKIE, PAGE } from '../helper.js'
17 17
 import {
18 18
   getUserIsConnected
@@ -22,6 +22,7 @@ import {
22 22
   setUserConnected
23 23
 } from '../action-creator.sync.js'
24 24
 import Cookies from 'js-cookie'
25
+import Dashboard from './Dashboard.jsx'
25 26
 
26 27
 class Tracim extends React.Component {
27 28
   constructor (props) {
@@ -40,6 +41,7 @@ class Tracim extends React.Component {
40 41
   }
41 42
 
42 43
   async componentDidMount () {
44
+    // console.log('<Tracim> did Mount')
43 45
     const { dispatch } = this.props
44 46
 
45 47
     const userFromCookies = {
@@ -66,27 +68,70 @@ class Tracim extends React.Component {
66 68
   handleRemoveFlashMessage = msg => this.props.dispatch(removeFlashMessage(msg))
67 69
 
68 70
   render () {
69
-    const { flashMessage, t } = this.props
71
+    const { props } = this
72
+
73
+    if (props.user.logged === null) return null // @TODO show loader
74
+
75
+    if (props.user.logged === false && props.location.pathname !== '/login') {
76
+      return <Redirect to={{pathname: '/login', state: {from: props.location}}} />
77
+    }
70 78
 
71 79
     return (
72 80
       <div className='tracim'>
73 81
         <Header />
74
-        <FlashMessage flashMessage={flashMessage} removeFlashMessage={this.handleRemoveFlashMessage} t={t} />
82
+        <FlashMessage flashMessage={props.flashMessage} removeFlashMessage={this.handleRemoveFlashMessage} t={props.t} />
75 83
 
76 84
         <div className='tracim__content'>
77 85
           <Route path={PAGE.LOGIN} component={Login} />
78 86
 
79
-          <PrivateRoute exact path='/' component={WorkspaceContent} />
80
-
81
-          <Switch>
82
-            <PrivateRoute path={PAGE.WORKSPACE.DASHBOARD(':idws')} component={Dashboard} />
83
-            <PrivateRoute path={PAGE.WORKSPACE.CALENDAR(':idws')} component={() => <div><br /><br /><br /><br />NYI</div>} />
84
-            <PrivateRoute path={PAGE.WORKSPACE.CONTENT(':idws', ':type?', ':idcts?')} component={WorkspaceContent} />
85
-          </Switch>
86
-
87
-          <PrivateRoute path={PAGE.ACCOUNT} component={Account} />
88
-          <PrivateRoute path={PAGE.ADMIN.ROOT} component={AppFullscreenManager} />
89
-          <PrivateRoute path={'/wip/:cp'} component={WIPcomponent} /> {/* for testing purpose only */}
87
+          <Route exact path='/' component={() => {
88
+            switch (props.user.logged) {
89
+              case true:
90
+                return <Redirect to={{pathname: PAGE.WORKSPACE.ROOT, state: {from: props.location}}} />
91
+              case false:
92
+                return <Redirect to={{pathname: '/login', state: {from: props.location}}} />
93
+              case null:
94
+                return null
95
+            }
96
+          }} />
97
+
98
+          <Route path='/workspaces/:idws?' render={() => // Workspace Router
99
+            <div className='sidebarpagecontainer'>
100
+              <Sidebar />
101
+
102
+              <Route exact path={PAGE.WORKSPACE.ROOT} render={() => props.workspaceList.length === 0 // handle '/' and redirect to first workspace
103
+                ? null // @FIXME this needs to be handled in case of new user that has no workspace
104
+                : <Redirect to={{pathname: `/workspaces/${props.workspaceList[0].id}/contents`, state: {from: props.location}}} />
105
+              } />
106
+
107
+              <Route exact path={`${PAGE.WORKSPACE.ROOT}/:idws`} render={props2 => // handle '/workspaces/:id' and add '/contents'
108
+                <Redirect to={{pathname: `/workspaces/${props2.match.params.idws}/contents`, state: {from: props.location}}} />
109
+              } />
110
+
111
+              <Route path={PAGE.WORKSPACE.DASHBOARD(':idws')} component={Dashboard} />
112
+              <Route path={PAGE.WORKSPACE.CALENDAR(':idws')} component={() => <div><br /><br /><br /><br />NYI</div>} />
113
+              <Route path={PAGE.WORKSPACE.CONTENT(':idws', ':type', ':idcts')} component={WorkspaceContent} />
114
+              <Route exact path={PAGE.WORKSPACE.CONTENT_LIST(':idws')} component={WorkspaceContent} />
115
+            </div>
116
+          } />
117
+
118
+          <Route path={PAGE.ACCOUNT} render={() =>
119
+            <div className='sidebarpagecontainer'>
120
+              <Sidebar />
121
+              <Account />
122
+            </div>
123
+          } />
124
+
125
+          <Route path={PAGE.ADMIN.ROOT} render={() =>
126
+            <div className='sidebarpagecontainer'>
127
+              <Sidebar />
128
+              <AppFullscreenManager />
129
+            </div>
130
+          } />
131
+
132
+          <Route path='/admin_temp/workspace' component={AdminWorkspacePage} />
133
+
134
+          <Route path={'/wip/:cp'} component={WIPcomponent} /> {/* for testing purpose only */}
90 135
 
91 136
           <div id='appFeatureContainer' />
92 137
         </div>
@@ -96,5 +141,5 @@ class Tracim extends React.Component {
96 141
   }
97 142
 }
98 143
 
99
-const mapStateToProps = ({ user, appList, contentType, flashMessage }) => ({ user, appList, contentType, flashMessage })
144
+const mapStateToProps = ({ user, appList, contentType, workspaceList, flashMessage }) => ({ user, appList, contentType, workspaceList, flashMessage })
100 145
 export default withRouter(connect(mapStateToProps)(translate()(Tracim)))

+ 2 - 5
frontend/src/container/WorkspaceContent.jsx Voir le fichier

@@ -3,7 +3,6 @@ import { connect } from 'react-redux'
3 3
 import { withRouter, Route } from 'react-router-dom'
4 4
 import appFactory from '../appFactory.js'
5 5
 import { PAGE } from '../helper.js'
6
-import Sidebar from './Sidebar.jsx'
7 6
 import Folder from '../component/Workspace/Folder.jsx'
8 7
 import ContentItem from '../component/Workspace/ContentItem.jsx'
9 8
 import ContentItemHeader from '../component/Workspace/ContentItemHeader.jsx'
@@ -108,7 +107,7 @@ class WorkspaceContent extends React.Component {
108 107
 
109 108
   handleClickContentItem = content => {
110 109
     console.log('%c<WorkspaceContent> content clicked', 'color: #c17838', content)
111
-    this.props.history.push(`/workspaces/${content.idWorkspace}/${content.type}/${content.id}`)
110
+    this.props.history.push(PAGE.WORKSPACE.CONTENT(content.idWorkspace, content.type, content.id))
112 111
   }
113 112
 
114 113
   handleClickEditContentItem = (e, content) => {
@@ -166,9 +165,7 @@ class WorkspaceContent extends React.Component {
166 165
       : []
167 166
 
168 167
     return (
169
-      <div className='sidebarpagecontainer'>
170
-        <Sidebar />
171
-
168
+      <div className='WorkspaceContent' style={{width: '100%'}}>
172 169
         <OpenContentApp
173 170
           // automatically open the app for the idContent in url
174 171
           idWorkspace={this.state.workspaceIdInUrl}

+ 11 - 0
frontend/src/css/AdminWorkspacePage.styl Voir le fichier

@@ -0,0 +1,11 @@
1
+.adminWorkspacePage
2
+  &__createworkspace
3
+    &__btncreate
4
+      margin 25px 15px
5
+  &__description
6
+    margin 25px 15px
7
+    font-size 20px
8
+  &__delimiter
9
+    margin 25px auto 65px auto
10
+  &__workspaceTable
11
+    margin 25px 15px

+ 0 - 1
frontend/src/css/Dashboard.styl Voir le fichier

@@ -31,7 +31,6 @@ coloricon()
31 31
     color lecteur
32 32
 
33 33
 .dashboard
34
-  margin-left 20px
35 34
   width 100%
36 35
   &__header
37 36
     flexwrap()

+ 1 - 1
frontend/src/css/Generic.styl Voir le fichier

@@ -111,7 +111,7 @@ a
111 111
 
112 112
 
113 113
 .pageContentGeneric
114
-  margin 10px 0 0 0
114
+  margin 10px 15px 0 15px
115 115
   width 100%
116 116
   height 100%
117 117
 

+ 6 - 0
frontend/src/css/Login.styl Voir le fichier

@@ -70,6 +70,12 @@
70 70
     &__text
71 71
       width 310px
72 72
       font-size 17px
73
+      color off-white
74
+      & > a
75
+        color off-white
76
+        font-size 19px
77
+        &:hover
78
+          text-decoration underline
73 79
 
74 80
 @media (min-width: min-lg) and (max-width: max-lg)
75 81
   .loginpage

+ 0 - 1
frontend/src/css/Workspace.styl Voir le fichier

@@ -4,7 +4,6 @@
4 4
     flex-wrap wrap
5 5
     margin-right 15px
6 6
   &__content
7
-    margin 0 15px
8 7
     &__button
9 8
       display flex
10 9
       justify-content flex-end

+ 3 - 5
frontend/src/helper.js Voir le fichier

@@ -24,14 +24,12 @@ export const workspaceConfig = {
24 24
 export const PAGE = {
25 25
   HOME: '/',
26 26
   WORKSPACE: {
27
+    ROOT: '/workspaces',
27 28
     DASHBOARD: (idws = ':idws') => `/workspaces/${idws}/dashboard`,
28
-    NEW: (idws, type) => `/workspaces/${idws}/${type}/new`,
29
+    NEW: (idws, type) => `/workspaces/${idws}/contents/${type}/new`,
29 30
     CALENDAR: (idws = ':idws') => `/workspaces/${idws}/calendar`,
30 31
     CONTENT_LIST: (idws = ':idws') => `/workspaces/${idws}/contents`,
31
-    CONTENT: (idws = ':idws', type = ':type?', idcts = ':idcts?') => `/workspaces/${idws}/${type}/${idcts}`, // @TODO add /contents/ in url and remove <Switch> in <Tracim>
32
-    // CONTENT_NEW: (idws = ':idws', ctstype = ':ctstype') => `/workspaces/${idws}/contents/${ctstype}/new`,
33
-    // CONTENT_EDIT: (idws = ':idws', idcts = ':idcts') => `/workspaces/${idws}/contents/${idcts}/edit`,
34
-    // CONTENT_TITLE_EDIT: (idws = ':idws', idcts = ':idcts') => `/workspaces/${idws}/contents/${idcts}/title/edit`,
32
+    CONTENT: (idws = ':idws', type = ':type', idcts = ':idcts') => `/workspaces/${idws}/contents/${type}/${idcts}`,
35 33
     ADMIN: (idws = ':idws') => `/workspaces/${idws}/admin`
36 34
   },
37 35
   LOGIN: '/login',

+ 1 - 1
frontend/src/reducer/user.js Voir le fichier

@@ -35,7 +35,7 @@ export default function user (state = defaultUser, action) {
35 35
       }
36 36
 
37 37
     case `${SET}/${USER_DISCONNECTED}`:
38
-      return defaultUser
38
+      return {...defaultUser, logged: false}
39 39
 
40 40
     case `${UPDATE}/${USER_DATA}`:
41 41
       return {...state, ...action.data}

+ 5 - 3
frontend/webpack.config.js Voir le fichier

@@ -28,10 +28,10 @@ module.exports = {
28 28
     ]
29 29
   },
30 30
   output: {
31
-    path: path.resolve(__dirname, 'dist'),
31
+    path: path.resolve(__dirname, 'dist/asset'),
32 32
     filename: 'tracim.app.entry.js',
33 33
     pathinfo: !isProduction,
34
-    publicPath: '/'
34
+    publicPath: '/asset/'
35 35
   },
36 36
   devServer: {
37 37
     contentBase: path.join(__dirname, 'dist/'),
@@ -70,8 +70,10 @@ module.exports = {
70 70
       use: ['style-loader', 'css-loader', 'stylus-loader']
71 71
     }, {
72 72
       test: /\.(jpg|png|svg)$/,
73
-      loader: 'url-loader',
73
+      loader: 'file-loader',
74 74
       options: {
75
+        name: '[name].[ext]',
76
+        outputPath: 'images/', // asset/ is in output.path
75 77
         limit: 2000
76 78
       }
77 79
     }]

+ 4 - 4
frontend_app_admin_workspace_user/build_admin_workspace_user.sh Voir le fichier

@@ -7,11 +7,11 @@ if [[ $1 = "-w" ]]; then
7 7
     windoz="windoz"
8 8
 fi
9 9
 
10
-log "npm run build$windoz"
10
+log "build frontend_app_admin_workspace_user"
11 11
 npm run build$windoz
12
-log "cp dist/admin_workspace_user.app.js ../frontend/dist/app"
12
+log "copying built file to frontend/"
13 13
 cp dist/admin_workspace_user.app.js ../frontend/dist/app
14
-log "cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json"
14
+log "copying en translation.json"
15 15
 cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json
16
-log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json"
16
+log "copying fr translation.json"
17 17
 cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json

+ 5 - 4
frontend_app_html-document/build_html-document.sh Voir le fichier

@@ -1,5 +1,6 @@
1 1
 #!/bin/bash
2 2
 
3
+# shellcheck disable=SC1091
3 4
 . ../bash_library.sh # source bash_library.sh
4 5
 
5 6
 windoz=""
@@ -7,11 +8,11 @@ if [[ $1 = "-w" ]]; then
7 8
     windoz="windoz"
8 9
 fi
9 10
 
10
-log "npm run build$windoz"
11
+log "build frontend_app_html-document"
11 12
 npm run build$windoz
12
-log "cp dist/html-document.app.js ../frontend/dist/app"
13
+log "copying built file to frontend/"
13 14
 cp dist/html-document.app.js ../frontend/dist/app
14
-log "cp i18next.scanner/en/translation.json ../frontend/dist/app/html-document_en_translation.json"
15
+log "copying en translation.json"
15 16
 cp i18next.scanner/en/translation.json ../frontend/dist/app/html-document_en_translation.json
16
-log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json"
17
+log "copying fr translation.json"
17 18
 cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json

+ 5 - 4
frontend_app_thread/build_thread.sh Voir le fichier

@@ -1,5 +1,6 @@
1 1
 #!/bin/bash
2 2
 
3
+# shellcheck disable=SC1091
3 4
 . ../bash_library.sh # source bash_library.sh
4 5
 
5 6
 windoz=""
@@ -7,11 +8,11 @@ if  [[ $1 = "-w" ]]; then
7 8
     windoz="windoz"
8 9
 fi
9 10
 
10
-log "npm run build$windoz"
11
+log "build frontend_app_thread"
11 12
 npm run build$windoz
12
-log "cp dist/thread.app.js ../frontend/dist/app"
13
+log "copying built file to frontend/"
13 14
 cp dist/thread.app.js ../frontend/dist/app
14
-log "cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json"
15
+log "copying en translation.json"
15 16
 cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json
16
-log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json"
17
+log "copying fr translation.json"
17 18
 cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json

+ 4 - 4
frontend_app_workspace/build_workspace.sh Voir le fichier

@@ -7,11 +7,11 @@ if [[ $1 = "-w" ]]; then
7 7
     windoz="windoz"
8 8
 fi
9 9
 
10
-log "npm run build$windoz"
10
+log "build frontend_app_workspace"
11 11
 npm run build$windoz
12
-log "cp dist/workspace.app.js ../frontend/dist/app"
12
+log "copying built file to frontend/"
13 13
 cp dist/workspace.app.js ../frontend/dist/app
14
-log "cp i18next.scanner/en/translation.json ../frontend/dist/app/workspace_en_translation.json"
14
+log "copying en translation.json"
15 15
 cp i18next.scanner/en/translation.json ../frontend/dist/app/workspace_en_translation.json
16
-log "cp i18next.scanner/fr/translation.json ../frontend/dist/app/workspace_fr_translation.json"
16
+log "copying fr translation.json"
17 17
 cp i18next.scanner/fr/translation.json ../frontend/dist/app/workspace_fr_translation.json

+ 49 - 40
install_frontend_dependencies.sh Voir le fichier

@@ -1,61 +1,70 @@
1 1
 #!/bin/bash
2 2
 
3
+# shellcheck disable=SC1091
3 4
 . bash_library.sh # source bash_library.sh
4 5
 
5 6
 # install Tracim Lib
6 7
 
7
-log "cd frontend_lib"
8
-cd frontend_lib
9
-log "npm i"
10
-npm i
11
-log "sudo npm link"
12
-sudo npm link
13
-cd -
8
+(
9
+  log "cd frontend_lib"
10
+  cd frontend_lib
11
+  log "npm i"
12
+  npm i
13
+  log "sudo npm link"
14
+  sudo npm link || exit
15
+)
14 16
 
15 17
 # install app Html Document
16 18
 
17
-log "cd frontend_app_html-document"
18
-cd frontend_app_html-document
19
-log "npm i"
20
-npm i
21
-log "npm link tracim_frontend_lib"
22
-npm link tracim_frontend_lib
23
-cd -
19
+(
20
+  log "cd frontend_app_html-document"
21
+  cd frontend_app_html-document
22
+  log "npm i"
23
+  npm i
24
+  log "npm link tracim_frontend_lib"
25
+  npm link tracim_frontend_lib
26
+)
24 27
 
25 28
 # install app Thread
26 29
 
27
-log "cd frontend_app_thread"
28
-cd frontend_app_thread
29
-log "npm i"
30
-npm i
31
-log "npm link tracim_frontend_lib"
32
-npm link tracim_frontend_lib
33
-cd -
30
+(
31
+  log "cd frontend_app_thread"
32
+  cd frontend_app_thread
33
+  log "npm i"
34
+  npm i
35
+  log "npm link tracim_frontend_lib"
36
+  npm link tracim_frontend_lib
37
+)
34 38
 
35 39
 # install app Workspace
36 40
 
37
-log "cd frontend_app_workspace"
38
-cd frontend_app_workspace
39
-log "npm i"
40
-npm i
41
-log "npm link tracim_frontend_lib"
42
-npm link tracim_frontend_lib
43
-cd -
41
+(
42
+  log "cd frontend_app_workspace"
43
+  cd frontend_app_workspace
44
+  log "npm i"
45
+  npm i
46
+  log "npm link tracim_frontend_lib"
47
+  npm link tracim_frontend_lib
48
+)
44 49
 
45 50
 # install app Admin Workspace User
46 51
 
47
-log "cd frontend_app_admin_workspace_user"
48
-cd frontend_app_admin_workspace_user
49
-log "npm i"
50
-npm i
51
-log "npm link tracim_frontend_lib"
52
-npm link tracim_frontend_lib
53
-cd -
52
+(
53
+  log "cd frontend_app_admin_workspace_user"
54
+  cd frontend_app_admin_workspace_user
55
+  log "npm i"
56
+  npm i
57
+  log "npm link tracim_frontend_lib"
58
+  npm link tracim_frontend_lib
59
+)
54 60
 
55 61
 # install Tracim Frontend
56 62
 
57
-log "cd frontend"
58
-cd frontend
59
-log "npm i"
60
-npm i
61
-cd -
63
+(
64
+  log "cd frontend"
65
+  cd frontend
66
+  log "npm i"
67
+  npm i
68
+  log "npm link tracim_frontend_lib"
69
+  npm link tracim_frontend_lib
70
+)