Browse Source

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

Guénaël Muller 6 years ago
parent
commit
1bdb21794d
96 changed files with 1438 additions and 368 deletions
  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 View File

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

+ 0 - 1
.travis.yml View File

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

+ 67 - 0
backend/tests_configs.ini View File

63
 email.notification.smtp.port = 1025
63
 email.notification.smtp.port = 1025
64
 email.notification.smtp.user = test_user
64
 email.notification.smtp.user = test_user
65
 email.notification.smtp.password = just_a_password
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 View File

161
 
161
 
162
         self.EMAIL_NOTIFICATION_FROM_EMAIL = settings.get(
162
         self.EMAIL_NOTIFICATION_FROM_EMAIL = settings.get(
163
             'email.notification.from.email',
163
             'email.notification.from.email',
164
+            'noreply+{user_id}@trac.im'
164
         )
165
         )
165
         self.EMAIL_NOTIFICATION_FROM_DEFAULT_LABEL = settings.get(
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
         self.EMAIL_NOTIFICATION_REPLY_TO_EMAIL = settings.get(
170
         self.EMAIL_NOTIFICATION_REPLY_TO_EMAIL = settings.get(
169
             'email.notification.reply_to.email',
171
             'email.notification.reply_to.email',

+ 4 - 0
backend/tracim_backend/exceptions.py View File

203
 
203
 
204
 class PreviewDimNotAllowed(TracimException):
204
 class PreviewDimNotAllowed(TracimException):
205
     pass
205
     pass
206
+
207
+
208
+class TooShortAutocompleteString(TracimException):
209
+    pass

+ 40 - 2
backend/tracim_backend/lib/core/user.py View File

3
 
3
 
4
 import transaction
4
 import transaction
5
 import typing as typing
5
 import typing as typing
6
+
7
+from sqlalchemy import or_
6
 from sqlalchemy.orm import Session
8
 from sqlalchemy.orm import Session
9
+from sqlalchemy.orm import Query
7
 from sqlalchemy.orm.exc import NoResultFound
10
 from sqlalchemy.orm.exc import NoResultFound
8
 
11
 
9
 from tracim_backend import CFG
12
 from tracim_backend import CFG
10
 from tracim_backend.models.auth import User
13
 from tracim_backend.models.auth import User
11
 from tracim_backend.models.auth import Group
14
 from tracim_backend.models.auth import Group
12
 from tracim_backend.exceptions import NoUserSetted
15
 from tracim_backend.exceptions import NoUserSetted
16
+from tracim_backend.exceptions import TooShortAutocompleteString
13
 from tracim_backend.exceptions import PasswordDoNotMatch
17
 from tracim_backend.exceptions import PasswordDoNotMatch
14
 from tracim_backend.exceptions import EmailValidationFailed
18
 from tracim_backend.exceptions import EmailValidationFailed
15
 from tracim_backend.exceptions import UserDoesNotExist
19
 from tracim_backend.exceptions import UserDoesNotExist
20
 from tracim_backend.models.context_models import UserInContext
24
 from tracim_backend.models.context_models import UserInContext
21
 from tracim_backend.lib.mail_notifier.notifier import get_email_manager
25
 from tracim_backend.lib.mail_notifier.notifier import get_email_manager
22
 from tracim_backend.models.context_models import TypeUser
26
 from tracim_backend.models.context_models import TypeUser
27
+from tracim_backend.models.data import UserRoleInWorkspace
23
 
28
 
24
 
29
 
25
 class UserApi(object):
30
 class UserApi(object):
94
             raise UserDoesNotExist('There is no current user')
99
             raise UserDoesNotExist('There is no current user')
95
         return self._user
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
     def get_all(self) -> typing.Iterable[User]:
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
     def find(
138
     def find(
101
             self,
139
             self,
196
         )
234
         )
197
         if do_save:
235
         if do_save:
198
             # TODO - G.M - 2018-07-24 - Check why commit is needed here
236
             # TODO - G.M - 2018-07-24 - Check why commit is needed here
199
-            transaction.commit()
237
+            self.save(user)
200
         return user
238
         return user
201
 
239
 
202
     def set_email(
240
     def set_email(

+ 24 - 0
backend/tracim_backend/lib/utils/utils.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 import datetime
2
 import datetime
3
+import random
4
+import string
3
 from redis import Redis
5
 from redis import Redis
4
 from rq import Queue
6
 from rq import Queue
5
 
7
 
72
     # webdav utils, it may cause trouble. So, it should be replaced to
74
     # webdav utils, it may cause trouble. So, it should be replaced to
73
     # a character which will not change in bdd.
75
     # a character which will not change in bdd.
74
     return datetime.datetime.now().isoformat().replace(':', '.')
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 View File

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
 _file = Application(
72
 _file = Application(
63
     label='Files',
73
     label='Files',
64
     slug='contents/file',
74
     slug='contents/file',
92
 # List of applications
102
 # List of applications
93
 applications = [
103
 applications = [
94
     html_documents,
104
     html_documents,
95
-    markdownpluspage,
105
+    # TODO - G.M - 2018-08-02 - Restore markdownpage app
106
+    # markdownpluspage,
96
     _file,
107
     _file,
97
     thread,
108
     thread,
98
-    calendar,
109
+    folder,
110
+    # calendar,
99
 ]
111
 ]

+ 6 - 4
backend/tracim_backend/models/contents.py View File

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

+ 8 - 0
backend/tracim_backend/models/context_models.py View File

186
         self.comment_id = comment_id
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
 class PageQuery(object):
197
 class PageQuery(object):
190
     """
198
     """
191
     Page query model
199
     Page query model

+ 8 - 11
backend/tracim_backend/tests/__init__.py View File

68
 class FunctionalTest(unittest.TestCase):
68
 class FunctionalTest(unittest.TestCase):
69
 
69
 
70
     fixtures = [BaseFixture]
70
     fixtures = [BaseFixture]
71
-    sqlalchemy_url = 'sqlite:///tracim_test.sqlite'
71
+    config_uri = 'tests_configs.ini'
72
+    config_section = 'functional_test'
72
 
73
 
73
     def setUp(self):
74
     def setUp(self):
74
         logger._logger.setLevel('WARNING')
75
         logger._logger.setLevel('WARNING')
76
+
75
         DepotManager._clear()
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
         hapic.reset_context()
82
         hapic.reset_context()
86
         self.engine = get_engine(self.settings)
83
         self.engine = get_engine(self.settings)
87
         DeclarativeBase.metadata.create_all(self.engine)
84
         DeclarativeBase.metadata.create_all(self.engine)
127
 
124
 
128
 
125
 
129
 class FunctionalTestNoDB(FunctionalTest):
126
 class FunctionalTestNoDB(FunctionalTest):
130
-    sqlalchemy_url = 'sqlite://'
127
+    config_section = 'functional_test_no_db'
131
 
128
 
132
     def init_database(self, settings):
129
     def init_database(self, settings):
133
         self.engine = get_engine(settings)
130
         self.engine = get_engine(settings)

+ 24 - 72
backend/tracim_backend/tests/functional/test_system.py View File

1
 # coding=utf-8
1
 # coding=utf-8
2
+from tracim_backend.models.contents import CONTENT_TYPES
2
 from tracim_backend.tests import FunctionalTest
3
 from tracim_backend.tests import FunctionalTest
4
+from tracim_backend.models.applications import applications
3
 
5
 
4
 """
6
 """
5
 Tests for /api/v2/system subpath endpoints.
7
 Tests for /api/v2/system subpath endpoints.
24
         )
26
         )
25
         res = self.testapp.get('/api/v2/system/applications', status=200)
27
         res = self.testapp.get('/api/v2/system/applications', status=200)
26
         res = res.json_body
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
     def test_api__get_applications__err_401__unregistered_user(self):
38
     def test_api__get_applications__err_401__unregistered_user(self):
64
         """
39
         """
96
         )
71
         )
97
         res = self.testapp.get('/api/v2/system/content_types', status=200)
72
         res = self.testapp.get('/api/v2/system/content_types', status=200)
98
         res = res.json_body
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
     def test_api__get_content_types__err_401__unregistered_user(self):
90
     def test_api__get_content_types__err_401__unregistered_user(self):
139
         """
91
         """

+ 412 - 15
backend/tracim_backend/tests/functional/test_user.py View File

2382
         assert workspace['workspace_id'] == 1
2382
         assert workspace['workspace_id'] == 1
2383
         assert workspace['label'] == 'Business'
2383
         assert workspace['label'] == 'Business'
2384
         assert workspace['slug'] == 'business'
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
         sidebar_entry = workspace['sidebar_entries'][0]
2389
         sidebar_entry = workspace['sidebar_entries'][0]
2388
         assert sidebar_entry['slug'] == 'dashboard'
2390
         assert sidebar_entry['slug'] == 'dashboard'
2389
         assert sidebar_entry['label'] == 'Dashboard'
2391
         assert sidebar_entry['label'] == 'Dashboard'
2406
         assert sidebar_entry['fa_icon'] == "file-text-o"
2408
         assert sidebar_entry['fa_icon'] == "file-text-o"
2407
 
2409
 
2408
         sidebar_entry = workspace['sidebar_entries'][3]
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
         assert sidebar_entry['slug'] == 'contents/file'
2411
         assert sidebar_entry['slug'] == 'contents/file'
2417
         assert sidebar_entry['label'] == 'Files'
2412
         assert sidebar_entry['label'] == 'Files'
2418
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
2413
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
2419
         assert sidebar_entry['hexcolor'] == "#FF9900"
2414
         assert sidebar_entry['hexcolor'] == "#FF9900"
2420
         assert sidebar_entry['fa_icon'] == "paperclip"
2415
         assert sidebar_entry['fa_icon'] == "paperclip"
2421
 
2416
 
2422
-        sidebar_entry = workspace['sidebar_entries'][5]
2417
+        sidebar_entry = workspace['sidebar_entries'][4]
2423
         assert sidebar_entry['slug'] == 'contents/thread'
2418
         assert sidebar_entry['slug'] == 'contents/thread'
2424
         assert sidebar_entry['label'] == 'Threads'
2419
         assert sidebar_entry['label'] == 'Threads'
2425
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
2420
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
2426
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
2421
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
2427
         assert sidebar_entry['fa_icon'] == "comments-o"
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
     def test_api__get_user_workspaces__err_403__unallowed_user(self):
2425
     def test_api__get_user_workspaces__err_403__unallowed_user(self):
2437
         """
2426
         """
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
 class TestSetEmailEndpoint(FunctionalTest):
3022
 class TestSetEmailEndpoint(FunctionalTest):
2650
     # -*- coding: utf-8 -*-
3023
     # -*- coding: utf-8 -*-
2651
     """
3024
     """
3025
             status=204,
3398
             status=204,
3026
         )
3399
         )
3027
         # Check After
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
         user = uapi.get_one(user_id)
3407
         user = uapi.get_one(user_id)
3029
         assert not user.validate_password('pass')
3408
         assert not user.validate_password('pass')
3030
         assert user.validate_password('mynewpassword')
3409
         assert user.validate_password('mynewpassword')
3080
             params=params,
3459
             params=params,
3081
             status=403,
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
         # Check After
3468
         # Check After
3084
         user = uapi.get_one(user_id)
3469
         user = uapi.get_one(user_id)
3085
         assert user.validate_password('pass')
3470
         assert user.validate_password('pass')
3138
             status=400,
3523
             status=400,
3139
         )
3524
         )
3140
         # Check After
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
         user = uapi.get_one(user_id)
3532
         user = uapi.get_one(user_id)
3142
         assert user.validate_password('pass')
3533
         assert user.validate_password('pass')
3143
         assert not user.validate_password('mynewpassword')
3534
         assert not user.validate_password('mynewpassword')
3195
             status=204,
3586
             status=204,
3196
         )
3587
         )
3197
         # Check After
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
         user = uapi.get_one(user_id)
3595
         user = uapi.get_one(user_id)
3199
         assert not user.validate_password('pass')
3596
         assert not user.validate_password('pass')
3200
         assert user.validate_password('mynewpassword')
3597
         assert user.validate_password('mynewpassword')

+ 98 - 27
backend/tracim_backend/tests/functional/test_workspaces.py View File

2
 """
2
 """
3
 Tests for /api/v2/workspaces subpath endpoints.
3
 Tests for /api/v2/workspaces subpath endpoints.
4
 """
4
 """
5
-
5
+import requests
6
 import transaction
6
 import transaction
7
 from depot.io.utils import FileIntent
7
 from depot.io.utils import FileIntent
8
 
8
 
41
         assert workspace['slug'] == 'business'
41
         assert workspace['slug'] == 'business'
42
         assert workspace['label'] == 'Business'
42
         assert workspace['label'] == 'Business'
43
         assert workspace['description'] == 'All importants documents'
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
         sidebar_entry = workspace['sidebar_entries'][0]
48
         sidebar_entry = workspace['sidebar_entries'][0]
47
         assert sidebar_entry['slug'] == 'dashboard'
49
         assert sidebar_entry['slug'] == 'dashboard'
48
         assert sidebar_entry['label'] == 'Dashboard'
50
         assert sidebar_entry['label'] == 'Dashboard'
65
         assert sidebar_entry['fa_icon'] == "file-text-o"
67
         assert sidebar_entry['fa_icon'] == "file-text-o"
66
 
68
 
67
         sidebar_entry = workspace['sidebar_entries'][3]
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
         assert sidebar_entry['slug'] == 'contents/file'
70
         assert sidebar_entry['slug'] == 'contents/file'
76
         assert sidebar_entry['label'] == 'Files'
71
         assert sidebar_entry['label'] == 'Files'
77
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
72
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=file"  # nopep8
78
         assert sidebar_entry['hexcolor'] == "#FF9900"
73
         assert sidebar_entry['hexcolor'] == "#FF9900"
79
         assert sidebar_entry['fa_icon'] == "paperclip"
74
         assert sidebar_entry['fa_icon'] == "paperclip"
80
 
75
 
81
-        sidebar_entry = workspace['sidebar_entries'][5]
76
+        sidebar_entry = workspace['sidebar_entries'][4]
82
         assert sidebar_entry['slug'] == 'contents/thread'
77
         assert sidebar_entry['slug'] == 'contents/thread'
83
         assert sidebar_entry['label'] == 'Threads'
78
         assert sidebar_entry['label'] == 'Threads'
84
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
79
         assert sidebar_entry['route'] == "/#/workspaces/1/contents?type=thread"  # nopep8
85
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
80
         assert sidebar_entry['hexcolor'] == "#ad4cf9"
86
         assert sidebar_entry['fa_icon'] == "comments-o"
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
     def test_api__update_workspace__ok_200__nominal_case(self) -> None:
83
     def test_api__update_workspace__ok_200__nominal_case(self) -> None:
96
         """
84
         """
97
         Test update workspace
85
         Test update workspace
118
         assert workspace['slug'] == 'business'
106
         assert workspace['slug'] == 'business'
119
         assert workspace['label'] == 'Business'
107
         assert workspace['label'] == 'Business'
120
         assert workspace['description'] == 'All importants documents'
108
         assert workspace['description'] == 'All importants documents'
121
-        assert len(workspace['sidebar_entries']) == 7
109
+        assert len(workspace['sidebar_entries']) == 5
122
 
110
 
123
         # modify workspace
111
         # modify workspace
124
         res = self.testapp.put_json(
112
         res = self.testapp.put_json(
132
         assert workspace['slug'] == 'superworkspace'
120
         assert workspace['slug'] == 'superworkspace'
133
         assert workspace['label'] == 'superworkspace'
121
         assert workspace['label'] == 'superworkspace'
134
         assert workspace['description'] == 'mysuperdescription'
122
         assert workspace['description'] == 'mysuperdescription'
135
-        assert len(workspace['sidebar_entries']) == 7
123
+        assert len(workspace['sidebar_entries']) == 5
136
 
124
 
137
         # after
125
         # after
138
         res = self.testapp.get(
126
         res = self.testapp.get(
145
         assert workspace['slug'] == 'superworkspace'
133
         assert workspace['slug'] == 'superworkspace'
146
         assert workspace['label'] == 'superworkspace'
134
         assert workspace['label'] == 'superworkspace'
147
         assert workspace['description'] == 'mysuperdescription'
135
         assert workspace['description'] == 'mysuperdescription'
148
-        assert len(workspace['sidebar_entries']) == 7
136
+        assert len(workspace['sidebar_entries']) == 5
149
 
137
 
150
     def test_api__update_workspace__err_400__empty_label(self) -> None:
138
     def test_api__update_workspace__err_400__empty_label(self) -> None:
151
         """
139
         """
612
         assert user_role['workspace_id'] == 1
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
 class TestWorkspaceContents(FunctionalTest):
686
 class TestWorkspaceContents(FunctionalTest):
616
     """
687
     """
617
     Tests for /api/v2/workspaces/{workspace_id}/contents endpoint
688
     Tests for /api/v2/workspaces/{workspace_id}/contents endpoint
1438
         params = {
1509
         params = {
1439
             'parent_id': None,
1510
             'parent_id': None,
1440
             'label': 'GenericCreatedContent',
1511
             'label': 'GenericCreatedContent',
1441
-            'content_type': 'markdownpage',
1512
+            'content_type': 'html-document',
1442
         }
1513
         }
1443
         res = self.testapp.post_json(
1514
         res = self.testapp.post_json(
1444
             '/api/v2/workspaces/1/contents',
1515
             '/api/v2/workspaces/1/contents',
1449
         assert res.json_body
1520
         assert res.json_body
1450
         assert res.json_body['status'] == 'open'
1521
         assert res.json_body['status'] == 'open'
1451
         assert res.json_body['content_id']
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
         assert res.json_body['is_archived'] is False
1524
         assert res.json_body['is_archived'] is False
1454
         assert res.json_body['is_deleted'] is False
1525
         assert res.json_body['is_deleted'] is False
1455
         assert res.json_body['workspace_id'] == 1
1526
         assert res.json_body['workspace_id'] == 1
1480
         )
1551
         )
1481
         params = {
1552
         params = {
1482
             'label': 'GenericCreatedContent',
1553
             'label': 'GenericCreatedContent',
1483
-            'content_type': 'markdownpage',
1554
+            'content_type': 'html-document',
1484
         }
1555
         }
1485
         res = self.testapp.post_json(
1556
         res = self.testapp.post_json(
1486
             '/api/v2/workspaces/1/contents',
1557
             '/api/v2/workspaces/1/contents',
1491
         assert res.json_body
1562
         assert res.json_body
1492
         assert res.json_body['status'] == 'open'
1563
         assert res.json_body['status'] == 'open'
1493
         assert res.json_body['content_id']
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
         assert res.json_body['is_archived'] is False
1566
         assert res.json_body['is_archived'] is False
1496
         assert res.json_body['is_deleted'] is False
1567
         assert res.json_body['is_deleted'] is False
1497
         assert res.json_body['workspace_id'] == 1
1568
         assert res.json_body['workspace_id'] == 1
1544
         )
1615
         )
1545
         params = {
1616
         params = {
1546
             'label': 'GenericCreatedContent',
1617
             'label': 'GenericCreatedContent',
1547
-            'content_type': 'markdownpage',
1618
+            'content_type': 'html-document',
1548
             'parent_id': 10,
1619
             'parent_id': 10,
1549
         }
1620
         }
1550
         res = self.testapp.post_json(
1621
         res = self.testapp.post_json(
1556
         assert res.json_body
1627
         assert res.json_body
1557
         assert res.json_body['status'] == 'open'
1628
         assert res.json_body['status'] == 'open'
1558
         assert res.json_body['content_id']
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
         assert res.json_body['is_archived'] is False
1631
         assert res.json_body['is_archived'] is False
1561
         assert res.json_body['is_deleted'] is False
1632
         assert res.json_body['is_deleted'] is False
1562
         assert res.json_body['workspace_id'] == 1
1633
         assert res.json_body['workspace_id'] == 1
1587
         )
1658
         )
1588
         params = {
1659
         params = {
1589
             'label': '',
1660
             'label': '',
1590
-            'content_type': 'markdownpage',
1661
+            'content_type': 'html-document',
1591
         }
1662
         }
1592
         res = self.testapp.post_json(
1663
         res = self.testapp.post_json(
1593
             '/api/v2/workspaces/1/contents',
1664
             '/api/v2/workspaces/1/contents',

+ 183 - 0
backend/tracim_backend/tests/library/test_user_api.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 import pytest
2
 import pytest
3
 import transaction
3
 import transaction
4
+from tracim_backend import models
4
 
5
 
5
 from tracim_backend.exceptions import AuthenticationFailed
6
 from tracim_backend.exceptions import AuthenticationFailed
7
+from tracim_backend.exceptions import TooShortAutocompleteString
6
 from tracim_backend.exceptions import UserDoesNotExist
8
 from tracim_backend.exceptions import UserDoesNotExist
7
 from tracim_backend.exceptions import UserNotActive
9
 from tracim_backend.exceptions import UserNotActive
8
 from tracim_backend.lib.core.group import GroupApi
10
 from tracim_backend.lib.core.group import GroupApi
9
 from tracim_backend.lib.core.user import UserApi
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
 from tracim_backend.models import User
14
 from tracim_backend.models import User
11
 from tracim_backend.models.context_models import UserInContext
15
 from tracim_backend.models.context_models import UserInContext
16
+from tracim_backend.models.data import UserRoleInWorkspace
12
 from tracim_backend.tests import DefaultTest
17
 from tracim_backend.tests import DefaultTest
13
 from tracim_backend.tests import eq_
18
 from tracim_backend.tests import eq_
14
 
19
 
107
         # u1 + Admin user from BaseFixture
112
         # u1 + Admin user from BaseFixture
108
         assert 2 == len(users)
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
     def test_unit__get_one__ok__nominal_case(self):
293
     def test_unit__get_one__ok__nominal_case(self):
111
         api = UserApi(
294
         api = UserApi(
112
             current_user=None,
295
             current_user=None,

+ 14 - 0
backend/tracim_backend/tests/library/tests_utils.py View File

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 View File

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

+ 28 - 0
backend/tracim_backend/views/contents_api/file_controller.py View File

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

+ 8 - 0
backend/tracim_backend/views/contents_api/html_document_controller.py View File

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

+ 8 - 0
backend/tracim_backend/views/contents_api/threads_controller.py View File

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

+ 13 - 0
backend/tracim_backend/views/core_api/schemas.py View File

2
 import marshmallow
2
 import marshmallow
3
 from marshmallow import post_load
3
 from marshmallow import post_load
4
 from marshmallow.validate import OneOf
4
 from marshmallow.validate import OneOf
5
+from marshmallow.validate import Length
5
 from marshmallow.validate import Range
6
 from marshmallow.validate import Range
6
 
7
 
7
 from tracim_backend.lib.utils.utils import DATETIME_FORMAT
8
 from tracim_backend.lib.utils.utils import DATETIME_FORMAT
11
 from tracim_backend.models.contents import CONTENT_TYPES
12
 from tracim_backend.models.contents import CONTENT_TYPES
12
 from tracim_backend.models.contents import open_status
13
 from tracim_backend.models.contents import open_status
13
 from tracim_backend.models.context_models import ActiveContentFilter
14
 from tracim_backend.models.context_models import ActiveContentFilter
15
+from tracim_backend.models.context_models import AutocompleteQuery
14
 from tracim_backend.models.context_models import ContentIdsQuery
16
 from tracim_backend.models.context_models import ContentIdsQuery
15
 from tracim_backend.models.context_models import UserWorkspaceAndContentPath
17
 from tracim_backend.models.context_models import UserWorkspaceAndContentPath
16
 from tracim_backend.models.context_models import ContentCreation
18
 from tracim_backend.models.context_models import ContentCreation
292
         return CommentPath(**data)
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
 class PageQuerySchema(marshmallow.Schema):
308
 class PageQuerySchema(marshmallow.Schema):
296
     page = marshmallow.fields.Int(
309
     page = marshmallow.fields.Int(
297
         example=2,
310
         example=2,

+ 57 - 0
backend/tracim_backend/views/core_api/user_controller.py View File

18
 from tracim_backend.exceptions import WrongUserPassword
18
 from tracim_backend.exceptions import WrongUserPassword
19
 from tracim_backend.exceptions import PasswordDoNotMatch
19
 from tracim_backend.exceptions import PasswordDoNotMatch
20
 from tracim_backend.views.core_api.schemas import UserSchema
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
 from tracim_backend.views.core_api.schemas import SetEmailSchema
23
 from tracim_backend.views.core_api.schemas import SetEmailSchema
22
 from tracim_backend.views.core_api.schemas import SetPasswordSchema
24
 from tracim_backend.views.core_api.schemas import SetPasswordSchema
23
 from tracim_backend.views.core_api.schemas import UserInfosSchema
25
 from tracim_backend.views.core_api.schemas import UserInfosSchema
37
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
39
 SWAGGER_TAG__USER_ENDPOINTS = 'Users'
38
 
40
 
39
 
41
 
42
+
40
 class UserController(Controller):
43
 class UserController(Controller):
41
 
44
 
42
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
45
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
77
         return uapi.get_user_with_context(request.candidate_user)
80
         return uapi.get_user_with_context(request.candidate_user)
78
 
81
 
79
     @hapic.with_api_doc(tags=[SWAGGER_TAG__USER_ENDPOINTS])
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
     @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
123
     @hapic.handle_exception(WrongUserPassword, HTTPStatus.FORBIDDEN)
81
     @require_same_user_or_profile(Group.TIM_ADMIN)
124
     @require_same_user_or_profile(Group.TIM_ADMIN)
82
     @hapic.input_body(SetEmailSchema())
125
     @hapic.input_body(SetEmailSchema())
329
         """
372
         """
330
         app_config = request.registry.settings['CFG']
373
         app_config = request.registry.settings['CFG']
331
         api = ContentApi(
374
         api = ContentApi(
375
+            show_archived=True,
376
+            show_deleted=True,
332
             current_user=request.candidate_user,
377
             current_user=request.candidate_user,
333
             session=request.dbsession,
378
             session=request.dbsession,
334
             config=app_config,
379
             config=app_config,
346
         """
391
         """
347
         app_config = request.registry.settings['CFG']
392
         app_config = request.registry.settings['CFG']
348
         api = ContentApi(
393
         api = ContentApi(
394
+            show_archived=True,
395
+            show_deleted=True,
349
             current_user=request.candidate_user,
396
             current_user=request.candidate_user,
350
             session=request.dbsession,
397
             session=request.dbsession,
351
             config=app_config,
398
             config=app_config,
363
         """
410
         """
364
         app_config = request.registry.settings['CFG']
411
         app_config = request.registry.settings['CFG']
365
         api = ContentApi(
412
         api = ContentApi(
413
+            show_archived=True,
414
+            show_deleted=True,
366
             current_user=request.candidate_user,
415
             current_user=request.candidate_user,
367
             session=request.dbsession,
416
             session=request.dbsession,
368
             config=app_config,
417
             config=app_config,
384
         configurator.add_route('user', '/users/{user_id}', request_method='GET')  # nopep8
433
         configurator.add_route('user', '/users/{user_id}', request_method='GET')  # nopep8
385
         configurator.add_view(self.user, route_name='user')
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
         # set user email
444
         # set user email
388
         configurator.add_route('set_user_email', '/users/{user_id}/email', request_method='PUT')  # nopep8
445
         configurator.add_route('set_user_email', '/users/{user_id}/email', request_method='PUT')  # nopep8
389
         configurator.add_view(self.set_user_email, route_name='set_user_email')
446
         configurator.add_view(self.set_user_email, route_name='set_user_email')

+ 17 - 2
backend/tracim_backend/views/core_api/workspace_controller.py View File

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

+ 1 - 1
bash_library.sh View File

5
 NC='\033[0m' # No Color
5
 NC='\033[0m' # No Color
6
 
6
 
7
 function log {
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 View File

1
 #!/bin/bash
1
 #!/bin/bash
2
 
2
 
3
+# shellcheck disable=SC1091
3
 . bash_library.sh # source bash_library.sh
4
 . bash_library.sh # source bash_library.sh
4
 
5
 
5
 windoz=""
6
 windoz=""
7
     windoz="windoz"
8
     windoz="windoz"
8
 fi
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
 # app Thread
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
 # app Workspace
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
 # app Admin Workspace User
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 View File

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

+ 3 - 1
frontend/.gitignore View File

1
 # Created by .ignore support plugin (hsz.mobi)
1
 # Created by .ignore support plugin (hsz.mobi)
2
-.idea/
3
 .git/
2
 .git/
4
 node_modules/
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 View File


frontend/dist/dev/bootstrap-4.0.0-beta.css → frontend/dist/asset/bootstrap/bootstrap-4.0.0-beta.css View File


frontend/dist/dev/jquery-3.2.1.js → frontend/dist/asset/bootstrap/jquery-3.2.1.js View File


frontend/dist/dev/popper-1.12.3.js → frontend/dist/asset/bootstrap/popper-1.12.3.js View File


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 View File


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 View File


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 View File


frontend/dist/font/font-awesome-4.7.0/fonts/FontAwesome.otf → frontend/dist/asset/font/font-awesome-4.7.0/fonts/FontAwesome.otf View File


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 View File


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 View File


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 View File


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 View File


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 View File


frontend/dist/font/font-awesome-4.7.0/less/animated.less → frontend/dist/asset/font/font-awesome-4.7.0/less/animated.less View File


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 View File


frontend/dist/font/font-awesome-4.7.0/less/core.less → frontend/dist/asset/font/font-awesome-4.7.0/less/core.less View File


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 View File


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 View File


frontend/dist/font/font-awesome-4.7.0/less/icons.less → frontend/dist/asset/font/font-awesome-4.7.0/less/icons.less View File


frontend/dist/font/font-awesome-4.7.0/less/larger.less → frontend/dist/asset/font/font-awesome-4.7.0/less/larger.less View File


frontend/dist/font/font-awesome-4.7.0/less/list.less → frontend/dist/asset/font/font-awesome-4.7.0/less/list.less View File


frontend/dist/font/font-awesome-4.7.0/less/mixins.less → frontend/dist/asset/font/font-awesome-4.7.0/less/mixins.less View File


frontend/dist/font/font-awesome-4.7.0/less/path.less → frontend/dist/asset/font/font-awesome-4.7.0/less/path.less View File


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 View File


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 View File


frontend/dist/font/font-awesome-4.7.0/less/stacked.less → frontend/dist/asset/font/font-awesome-4.7.0/less/stacked.less View File


frontend/dist/font/font-awesome-4.7.0/less/variables.less → frontend/dist/asset/font/font-awesome-4.7.0/less/variables.less View File


frontend/dist/font/font-awesome-4.7.0/scss/_animated.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_animated.scss View File


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 View File


frontend/dist/font/font-awesome-4.7.0/scss/_core.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_core.scss View File


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 View File


frontend/dist/font/font-awesome-4.7.0/scss/_icons.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_icons.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_larger.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_larger.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_list.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_list.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_mixins.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_mixins.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_path.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_path.scss View File


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 View File


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 View File


frontend/dist/font/font-awesome-4.7.0/scss/_stacked.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_stacked.scss View File


frontend/dist/font/font-awesome-4.7.0/scss/_variables.scss → frontend/dist/asset/font/font-awesome-4.7.0/scss/_variables.scss View File


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 View File


frontend/dist/appInterface.js → frontend/dist/asset/tracim/appInterface.js View File

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

frontend/dist/tinymceInit.js → frontend/dist/asset/tracim/tinymceInit.js View File


BIN
frontend/dist/ecbb61e619a4d2801db1054c019316cc.jpg View File


+ 16 - 17
frontend/dist/index.html View File

6
     <title>Tracim</title>
6
     <title>Tracim</title>
7
     <link rel='shortcut icon' type='image/x-icon' href='/asset/favicon.ico' >
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
     <link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
10
     <link href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700" rel="stylesheet">
11
     <!--
11
     <!--
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">
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
     <link rel="stylesheet" type="text/css" href="/asset/hamburger/hamburgers.min.css">
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
     <style>
17
     <style>
18
       /* code bellow will be generated by backend */
18
       /* code bellow will be generated by backend */
19
       .primaryColorFont { color: #7d4e24; }
19
       .primaryColorFont { color: #7d4e24; }
20
       .primaryColorFontDarken { color: #572800; }
20
       .primaryColorFontDarken { color: #572800; }
21
       .primaryColorFontLighten { color: #a3744a; }
21
       .primaryColorFontLighten { color: #a3744a; }
22
-      .whiteFontColor {color: #fdfdfd;}
23
 
22
 
24
       .primaryColorFontHover:hover { color: #7d4e24; }
23
       .primaryColorFontHover:hover { color: #7d4e24; }
25
       .primaryColorFontDarkenHover:hover { color: #572800; }
24
       .primaryColorFontDarkenHover:hover { color: #572800; }
47
   <body>
46
   <body>
48
     <div id='content'></div>
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
   </body>
67
   </body>
69
 </html>
68
 </html>

+ 5 - 12
frontend/i18next.scanner/en/translation.json View File

37
   "Change your status": "Change your status",
37
   "Change your status": "Change your status",
38
   "subscriber": "subscriber",
38
   "subscriber": "subscriber",
39
   "unsubscribed": "unsubscribed",
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
   "Recent activity": "Recent activity",
40
   "Recent activity": "Recent activity",
46
   "Mark everything as read": "Mark everything as read",
41
   "Mark everything as read": "Mark everything as read",
47
   "See more": "See more",
42
   "See more": "See more",
50
   "Enter the name or email of the member": "Enter the name or email of the member",
45
   "Enter the name or email of the member": "Enter the name or email of the member",
51
   "Create an account": "Create an account",
46
   "Create an account": "Create an account",
52
   "Choose the role of the member": "Choose the role of the member",
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
   "Validate": "Validate",
48
   "Validate": "Validate",
58
   "Implement Tracim in your explorer": "Implement Tracim in your explorer",
49
   "Implement Tracim in your explorer": "Implement Tracim in your explorer",
59
   "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.",
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
   "Workspace Calendar": "Workspace Calendar",
51
   "Workspace Calendar": "Workspace Calendar",
61
   "Each workspace has its own calendar.": "Each workspace has its own calendar.",
52
   "Each workspace has its own calendar.": "Each workspace has its own calendar.",
62
   "Email Adress": "Email Adress",
53
   "Email Adress": "Email Adress",
63
-  "Explore the workspace": "Explore the workspace",
64
   "Old password": "Old password",
54
   "Old password": "Old password",
65
   "New password": "New password",
55
   "New password": "New password",
66
   "Name:": "Name:",
56
   "Name:": "Name:",
67
   "Email Adress:": "Email Adress:",
57
   "Email Adress:": "Email Adress:",
68
-  "An error has happened": "An error has happened",
69
   "You have subscribed to this workspace's notifications": "You have subscribed to this workspace's notifications",
58
   "You have subscribed to this workspace's notifications": "You have subscribed to this workspace's notifications",
70
   "Connection": "Connection",
59
   "Connection": "Connection",
71
   "Forgotten password ?": "Forgotten password ?",
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 View File

37
   "Change your status": "Changer de status",
37
   "Change your status": "Changer de status",
38
   "subscriber": "Abonné(e)",
38
   "subscriber": "Abonné(e)",
39
   "unsubscribed": "Non Abonné(e)",
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
   "Recent activity": "Activité récente",
40
   "Recent activity": "Activité récente",
47
   "Mark everything as read": "Tout marquer comme lu",
41
   "Mark everything as read": "Tout marquer comme lu",
48
   "See more": "Voir plus",
42
   "See more": "Voir plus",
50
   "Add a member": "Ajouter un membre",
44
   "Add a member": "Ajouter un membre",
51
   "Enter the name or email of the member": "Renseigner le nom ou l'email de l'utilisateur",
45
   "Enter the name or email of the member": "Renseigner le nom ou l'email de l'utilisateur",
52
   "Create an account": "Créer un compte",
46
   "Create an account": "Créer un compte",
53
-  "Supervisor": "Responsable",
54
-  "Content Manager": "Gestionnaire de contenu",
55
-  "Contributor": "Contributeur",
56
-  "Reader": "Lecteur",
57
   "Validate": "Validé",
47
   "Validate": "Validé",
58
   "Implement Tracim in your explorer": "Implémenter Tracim dans votre explorateur",
48
   "Implement Tracim in your explorer": "Implémenter Tracim dans votre explorateur",
59
   "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.",
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
   "New password": "Nouveau mot de passe",
55
   "New password": "Nouveau mot de passe",
66
   "Name:": "Nom :",
56
   "Name:": "Nom :",
67
   "Email Adress:": "Adresse mail :",
57
   "Email Adress:": "Adresse mail :",
68
-  "An error has happened": "__NOT_TRANSLATED__",
69
   "You have subscribed to this workspace's notifications": "Vous êtes abonné aux notifications de cet espace de travail.",
58
   "You have subscribed to this workspace's notifications": "Vous êtes abonné aux notifications de cet espace de travail.",
70
   "Connection": "Connexion",
59
   "Connection": "Connexion",
71
   "Forgotten password ?": "Mot de passe oublié ?",
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 View File

22
       console.log('%c<OpenContentApp> contentToOpen', 'color: #dcae84', contentToOpen)
22
       console.log('%c<OpenContentApp> contentToOpen', 'color: #dcae84', contentToOpen)
23
 
23
 
24
       if (appOpenedType === contentToOpen.type) { // app already open
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
       } else { // open another app
26
       } else { // open another app
30
         // if another app is already visible, hide it
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
         // open app
29
         // open app
33
         renderAppFeature(
30
         renderAppFeature(
34
           contentType.find(ct => ct.slug === contentToOpen.type),
31
           contentType.find(ct => ct.slug === contentToOpen.type),

+ 1 - 4
frontend/src/container/Account.jsx View File

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

+ 138 - 0
frontend/src/container/AdminWorkspacePage.jsx View File

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 View File

4
 import { Route } from 'react-router-dom'
4
 import { Route } from 'react-router-dom'
5
 import { PAGE } from '../helper.js'
5
 import { PAGE } from '../helper.js'
6
 import appFactory from '../appFactory.js'
6
 import appFactory from '../appFactory.js'
7
-import Sidebar from './Sidebar.jsx'
8
 
7
 
9
 class AppFullscreenManager extends React.Component {
8
 class AppFullscreenManager extends React.Component {
10
   constructor (props) {
9
   constructor (props) {
11
     super(props)
10
     super(props)
12
     this.state = {
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
   render () {
18
   render () {
20
     const { props } = this
19
     const { props } = this
21
 
20
 
22
     return (
21
     return (
23
-      <div className='sidebarpagecontainer'>
24
-        <Sidebar />
25
-
22
+      <div className='AppFullScreenManager'>
26
         <div id='appFullscreenContainer' />
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
           <div className='emptyDiForRoute'>
26
           <div className='emptyDiForRoute'>
30
             <Route path={PAGE.ADMIN.WORKSPACE} render={() => {
27
             <Route path={PAGE.ADMIN.WORKSPACE} render={() => {
31
               props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'workspace'}, props.user, {})
28
               props.renderAppFullscreen({slug: 'admin_workspace_user', hexcolor: '#7d4e24', type: 'workspace'}, props.user, {})

+ 3 - 6
frontend/src/container/Dashboard.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import { connect } from 'react-redux'
2
 import { connect } from 'react-redux'
3
-import Sidebar from './Sidebar.jsx'
4
 import { translate } from 'react-i18next'
3
 import { translate } from 'react-i18next'
5
 import {
4
 import {
6
   PageWrapper,
5
   PageWrapper,
45
       case 200:
44
       case 200:
46
         props.dispatch(setWorkspaceDetail(fetchWorkspaceDetail.json)); break
45
         props.dispatch(setWorkspaceDetail(fetchWorkspaceDetail.json)); break
47
       default:
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
     const fetchWorkspaceMemberList = await props.dispatch(getWorkspaceMemberList(props.user, state.workspaceIdInUrl))
50
     const fetchWorkspaceMemberList = await props.dispatch(getWorkspaceMemberList(props.user, state.workspaceIdInUrl))
93
     const { props, state } = this
92
     const { props, state } = this
94
 
93
 
95
     return (
94
     return (
96
-      <div className='sidebarpagecontainer'>
97
-        <Sidebar />
98
-
95
+      <div className='Dashboard' style={{width: '100%'}}>
99
         <PageWrapper customeClass='dashboard'>
96
         <PageWrapper customeClass='dashboard'>
100
           <PageTitle
97
           <PageTitle
101
             parentClass='dashboard__header'
98
             parentClass='dashboard__header'
124
               <div className='dashboard__userstatut'>
121
               <div className='dashboard__userstatut'>
125
                 <div className='dashboard__userstatut__role'>
122
                 <div className='dashboard__userstatut__role'>
126
                   <div className='dashboard__userstatut__role__msg'>
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
                   </div>
125
                   </div>
129
 
126
 
130
                   {(() => {
127
                   {(() => {

+ 4 - 4
frontend/src/container/Login.jsx View File

55
       Cookies.set(COOKIE.USER_LOGIN, inputLogin.value)
55
       Cookies.set(COOKIE.USER_LOGIN, inputLogin.value)
56
       Cookies.set(COOKIE.USER_AUTH, userAuth)
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
       dispatch(newFlashMessage(t('Email or password invalid'), 'danger'))
60
       dispatch(newFlashMessage(t('Email or password invalid'), 'danger'))
61
     }
61
     }
62
   }
62
   }
140
           </div>
140
           </div>
141
 
141
 
142
           <footer className='loginpage__footer'>
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
             </div>
145
             </div>
146
           </footer>
146
           </footer>
147
 
147
 

+ 7 - 4
frontend/src/container/Sidebar.jsx View File

25
     super(props)
25
     super(props)
26
     this.state = {
26
     this.state = {
27
       sidebarClose: false,
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
     document.addEventListener('appCustomEvent', this.customEventReducer)
31
     document.addEventListener('appCustomEvent', this.customEventReducer)
41
   }
41
   }
42
 
42
 
43
   componentDidMount () {
43
   componentDidMount () {
44
+    // console.log('Sidebar Did Mount', this.props)
44
     this.loadAppConfigAndWorkspaceList()
45
     this.loadAppConfigAndWorkspaceList()
45
   }
46
   }
46
 
47
 
47
   componentDidUpdate (prevProps, prevState) {
48
   componentDidUpdate (prevProps, prevState) {
49
+    const { props } = this
50
+
48
     // console.log('%c<Sidebar> Did Update', 'color: #c17838')
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
     if (prevState.workspaceIdInUrl !== newWorkspaceId) this.setState({workspaceIdInUrl: newWorkspaceId})
55
     if (prevState.workspaceIdInUrl !== newWorkspaceId) this.setState({workspaceIdInUrl: newWorkspaceId})
53
   }
56
   }
54
 
57
 
104
     return (
107
     return (
105
       <div className={classnames('sidebar primaryColorBgDarken', {'sidebarclose': sidebarClose})}>
108
       <div className={classnames('sidebar primaryColorBgDarken', {'sidebarclose': sidebarClose})}>
106
         <div className='sidebarSticky'>
109
         <div className='sidebarSticky'>
107
-          <div className='sidebar__expand primaryColorBg whiteColorBorder' onClick={this.handleClickToggleSidebar}>
110
+          <div className='sidebar__expand primaryColorBg' onClick={this.handleClickToggleSidebar}>
108
             <i className={classnames('fa fa-chevron-left', {'fa-chevron-right': sidebarClose, 'fa-chevron-left': !sidebarClose})} />
111
             <i className={classnames('fa fa-chevron-left', {'fa-chevron-right': sidebarClose, 'fa-chevron-left': !sidebarClose})} />
109
           </div>
112
           </div>
110
 
113
 

+ 62 - 17
frontend/src/container/Tracim.jsx View File

1
 import React from 'react'
1
 import React from 'react'
2
 import { connect } from 'react-redux'
2
 import { connect } from 'react-redux'
3
 import { translate } from 'react-i18next'
3
 import { translate } from 'react-i18next'
4
+import Sidebar from './Sidebar.jsx'
4
 import Header from './Header.jsx'
5
 import Header from './Header.jsx'
5
 import Login from './Login.jsx'
6
 import Login from './Login.jsx'
6
-import Dashboard from './Dashboard.jsx'
7
 import Account from './Account.jsx'
7
 import Account from './Account.jsx'
8
+import AdminWorkspacePage from './AdminWorkspacePage.jsx'
8
 import AppFullscreenManager from './AppFullscreenManager.jsx'
9
 import AppFullscreenManager from './AppFullscreenManager.jsx'
9
 import FlashMessage from '../component/FlashMessage.jsx'
10
 import FlashMessage from '../component/FlashMessage.jsx'
10
 import WorkspaceContent from './WorkspaceContent.jsx'
11
 import WorkspaceContent from './WorkspaceContent.jsx'
11
 import WIPcomponent from './WIPcomponent.jsx'
12
 import WIPcomponent from './WIPcomponent.jsx'
12
 import {
13
 import {
13
-  Route, withRouter, Switch
14
+  Route, withRouter, Redirect
14
 } from 'react-router-dom'
15
 } from 'react-router-dom'
15
-import PrivateRoute from './PrivateRoute.jsx'
16
 import { COOKIE, PAGE } from '../helper.js'
16
 import { COOKIE, PAGE } from '../helper.js'
17
 import {
17
 import {
18
   getUserIsConnected
18
   getUserIsConnected
22
   setUserConnected
22
   setUserConnected
23
 } from '../action-creator.sync.js'
23
 } from '../action-creator.sync.js'
24
 import Cookies from 'js-cookie'
24
 import Cookies from 'js-cookie'
25
+import Dashboard from './Dashboard.jsx'
25
 
26
 
26
 class Tracim extends React.Component {
27
 class Tracim extends React.Component {
27
   constructor (props) {
28
   constructor (props) {
40
   }
41
   }
41
 
42
 
42
   async componentDidMount () {
43
   async componentDidMount () {
44
+    // console.log('<Tracim> did Mount')
43
     const { dispatch } = this.props
45
     const { dispatch } = this.props
44
 
46
 
45
     const userFromCookies = {
47
     const userFromCookies = {
66
   handleRemoveFlashMessage = msg => this.props.dispatch(removeFlashMessage(msg))
68
   handleRemoveFlashMessage = msg => this.props.dispatch(removeFlashMessage(msg))
67
 
69
 
68
   render () {
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
     return (
79
     return (
72
       <div className='tracim'>
80
       <div className='tracim'>
73
         <Header />
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
         <div className='tracim__content'>
84
         <div className='tracim__content'>
77
           <Route path={PAGE.LOGIN} component={Login} />
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
           <div id='appFeatureContainer' />
136
           <div id='appFeatureContainer' />
92
         </div>
137
         </div>
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
 export default withRouter(connect(mapStateToProps)(translate()(Tracim)))
145
 export default withRouter(connect(mapStateToProps)(translate()(Tracim)))

+ 2 - 5
frontend/src/container/WorkspaceContent.jsx View File

3
 import { withRouter, Route } from 'react-router-dom'
3
 import { withRouter, Route } from 'react-router-dom'
4
 import appFactory from '../appFactory.js'
4
 import appFactory from '../appFactory.js'
5
 import { PAGE } from '../helper.js'
5
 import { PAGE } from '../helper.js'
6
-import Sidebar from './Sidebar.jsx'
7
 import Folder from '../component/Workspace/Folder.jsx'
6
 import Folder from '../component/Workspace/Folder.jsx'
8
 import ContentItem from '../component/Workspace/ContentItem.jsx'
7
 import ContentItem from '../component/Workspace/ContentItem.jsx'
9
 import ContentItemHeader from '../component/Workspace/ContentItemHeader.jsx'
8
 import ContentItemHeader from '../component/Workspace/ContentItemHeader.jsx'
108
 
107
 
109
   handleClickContentItem = content => {
108
   handleClickContentItem = content => {
110
     console.log('%c<WorkspaceContent> content clicked', 'color: #c17838', content)
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
   handleClickEditContentItem = (e, content) => {
113
   handleClickEditContentItem = (e, content) => {
166
       : []
165
       : []
167
 
166
 
168
     return (
167
     return (
169
-      <div className='sidebarpagecontainer'>
170
-        <Sidebar />
171
-
168
+      <div className='WorkspaceContent' style={{width: '100%'}}>
172
         <OpenContentApp
169
         <OpenContentApp
173
           // automatically open the app for the idContent in url
170
           // automatically open the app for the idContent in url
174
           idWorkspace={this.state.workspaceIdInUrl}
171
           idWorkspace={this.state.workspaceIdInUrl}

+ 11 - 0
frontend/src/css/AdminWorkspacePage.styl View File

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 View File

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

+ 1 - 1
frontend/src/css/Generic.styl View File

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

+ 6 - 0
frontend/src/css/Login.styl View File

70
     &__text
70
     &__text
71
       width 310px
71
       width 310px
72
       font-size 17px
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
 @media (min-width: min-lg) and (max-width: max-lg)
80
 @media (min-width: min-lg) and (max-width: max-lg)
75
   .loginpage
81
   .loginpage

+ 0 - 1
frontend/src/css/Workspace.styl View File

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

+ 3 - 5
frontend/src/helper.js View File

24
 export const PAGE = {
24
 export const PAGE = {
25
   HOME: '/',
25
   HOME: '/',
26
   WORKSPACE: {
26
   WORKSPACE: {
27
+    ROOT: '/workspaces',
27
     DASHBOARD: (idws = ':idws') => `/workspaces/${idws}/dashboard`,
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
     CALENDAR: (idws = ':idws') => `/workspaces/${idws}/calendar`,
30
     CALENDAR: (idws = ':idws') => `/workspaces/${idws}/calendar`,
30
     CONTENT_LIST: (idws = ':idws') => `/workspaces/${idws}/contents`,
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
     ADMIN: (idws = ':idws') => `/workspaces/${idws}/admin`
33
     ADMIN: (idws = ':idws') => `/workspaces/${idws}/admin`
36
   },
34
   },
37
   LOGIN: '/login',
35
   LOGIN: '/login',

+ 1 - 1
frontend/src/reducer/user.js View File

35
       }
35
       }
36
 
36
 
37
     case `${SET}/${USER_DISCONNECTED}`:
37
     case `${SET}/${USER_DISCONNECTED}`:
38
-      return defaultUser
38
+      return {...defaultUser, logged: false}
39
 
39
 
40
     case `${UPDATE}/${USER_DATA}`:
40
     case `${UPDATE}/${USER_DATA}`:
41
       return {...state, ...action.data}
41
       return {...state, ...action.data}

+ 5 - 3
frontend/webpack.config.js View File

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

+ 4 - 4
frontend_app_admin_workspace_user/build_admin_workspace_user.sh View File

7
     windoz="windoz"
7
     windoz="windoz"
8
 fi
8
 fi
9
 
9
 
10
-log "npm run build$windoz"
10
+log "build frontend_app_admin_workspace_user"
11
 npm run build$windoz
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
 cp dist/admin_workspace_user.app.js ../frontend/dist/app
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
 cp i18next.scanner/en/translation.json ../frontend/dist/app/admin_workspace_user_en_translation.json
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
 cp i18next.scanner/fr/translation.json ../frontend/dist/app/admin_workspace_user_fr_translation.json
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 View File

1
 #!/bin/bash
1
 #!/bin/bash
2
 
2
 
3
+# shellcheck disable=SC1091
3
 . ../bash_library.sh # source bash_library.sh
4
 . ../bash_library.sh # source bash_library.sh
4
 
5
 
5
 windoz=""
6
 windoz=""
7
     windoz="windoz"
8
     windoz="windoz"
8
 fi
9
 fi
9
 
10
 
10
-log "npm run build$windoz"
11
+log "build frontend_app_html-document"
11
 npm run build$windoz
12
 npm run build$windoz
12
-log "cp dist/html-document.app.js ../frontend/dist/app"
13
+log "copying built file to frontend/"
13
 cp dist/html-document.app.js ../frontend/dist/app
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
 cp i18next.scanner/en/translation.json ../frontend/dist/app/html-document_en_translation.json
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
 cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json
18
 cp i18next.scanner/fr/translation.json ../frontend/dist/app/html-document_fr_translation.json

+ 5 - 4
frontend_app_thread/build_thread.sh View File

1
 #!/bin/bash
1
 #!/bin/bash
2
 
2
 
3
+# shellcheck disable=SC1091
3
 . ../bash_library.sh # source bash_library.sh
4
 . ../bash_library.sh # source bash_library.sh
4
 
5
 
5
 windoz=""
6
 windoz=""
7
     windoz="windoz"
8
     windoz="windoz"
8
 fi
9
 fi
9
 
10
 
10
-log "npm run build$windoz"
11
+log "build frontend_app_thread"
11
 npm run build$windoz
12
 npm run build$windoz
12
-log "cp dist/thread.app.js ../frontend/dist/app"
13
+log "copying built file to frontend/"
13
 cp dist/thread.app.js ../frontend/dist/app
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
 cp i18next.scanner/en/translation.json ../frontend/dist/app/thread_en_translation.json
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
 cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json
18
 cp i18next.scanner/fr/translation.json ../frontend/dist/app/thread_fr_translation.json

+ 4 - 4
frontend_app_workspace/build_workspace.sh View File

7
     windoz="windoz"
7
     windoz="windoz"
8
 fi
8
 fi
9
 
9
 
10
-log "npm run build$windoz"
10
+log "build frontend_app_workspace"
11
 npm run build$windoz
11
 npm run build$windoz
12
-log "cp dist/workspace.app.js ../frontend/dist/app"
12
+log "copying built file to frontend/"
13
 cp dist/workspace.app.js ../frontend/dist/app
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
 cp i18next.scanner/en/translation.json ../frontend/dist/app/workspace_en_translation.json
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
 cp i18next.scanner/fr/translation.json ../frontend/dist/app/workspace_fr_translation.json
17
 cp i18next.scanner/fr/translation.json ../frontend/dist/app/workspace_fr_translation.json

+ 49 - 40
install_frontend_dependencies.sh View File

1
 #!/bin/bash
1
 #!/bin/bash
2
 
2
 
3
+# shellcheck disable=SC1091
3
 . bash_library.sh # source bash_library.sh
4
 . bash_library.sh # source bash_library.sh
4
 
5
 
5
 # install Tracim Lib
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
 # install app Html Document
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
 # install app Thread
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
 # install app Workspace
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
 # install app Admin Workspace User
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
 # install Tracim Frontend
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
+)