zou 0.19.15__py3-none-any.whl → 0.20.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. zou/__init__.py +1 -1
  2. zou/app/__init__.py +10 -2
  3. zou/app/api.py +2 -0
  4. zou/app/blueprints/assets/__init__.py +22 -0
  5. zou/app/blueprints/assets/resources.py +241 -4
  6. zou/app/blueprints/auth/__init__.py +4 -0
  7. zou/app/blueprints/auth/resources.py +154 -22
  8. zou/app/blueprints/breakdown/resources.py +4 -4
  9. zou/app/blueprints/chats/__init__.py +22 -0
  10. zou/app/blueprints/chats/resources.py +199 -0
  11. zou/app/blueprints/comments/resources.py +36 -19
  12. zou/app/blueprints/crud/__init__.py +12 -0
  13. zou/app/blueprints/crud/attachment_file.py +14 -5
  14. zou/app/blueprints/crud/base.py +29 -28
  15. zou/app/blueprints/crud/chat.py +13 -0
  16. zou/app/blueprints/crud/chat_message.py +13 -0
  17. zou/app/blueprints/crud/comments.py +85 -29
  18. zou/app/blueprints/crud/custom_action.py +1 -1
  19. zou/app/blueprints/crud/day_off.py +47 -9
  20. zou/app/blueprints/crud/department.py +1 -25
  21. zou/app/blueprints/crud/entity.py +46 -5
  22. zou/app/blueprints/crud/entity_type.py +13 -1
  23. zou/app/blueprints/crud/event.py +1 -1
  24. zou/app/blueprints/crud/file_status.py +1 -1
  25. zou/app/blueprints/crud/metadata_descriptor.py +24 -10
  26. zou/app/blueprints/crud/organisation.py +22 -5
  27. zou/app/blueprints/crud/output_file.py +1 -1
  28. zou/app/blueprints/crud/output_type.py +1 -1
  29. zou/app/blueprints/crud/person.py +32 -24
  30. zou/app/blueprints/crud/playlist.py +1 -1
  31. zou/app/blueprints/crud/preview_background_file.py +6 -7
  32. zou/app/blueprints/crud/preview_file.py +1 -1
  33. zou/app/blueprints/crud/project.py +14 -6
  34. zou/app/blueprints/crud/project_status.py +1 -1
  35. zou/app/blueprints/crud/schedule_item.py +4 -2
  36. zou/app/blueprints/crud/software.py +1 -1
  37. zou/app/blueprints/crud/status_automation.py +1 -1
  38. zou/app/blueprints/crud/studio.py +33 -0
  39. zou/app/blueprints/crud/task.py +47 -3
  40. zou/app/blueprints/crud/task_status.py +1 -1
  41. zou/app/blueprints/crud/task_type.py +4 -4
  42. zou/app/blueprints/crud/working_file.py +4 -8
  43. zou/app/blueprints/events/resources.py +13 -12
  44. zou/app/blueprints/export/csv/assets.py +15 -6
  45. zou/app/blueprints/export/csv/edits.py +15 -5
  46. zou/app/blueprints/export/csv/playlists.py +1 -1
  47. zou/app/blueprints/export/csv/shots.py +15 -5
  48. zou/app/blueprints/export/csv/time_spents.py +1 -1
  49. zou/app/blueprints/files/resources.py +22 -23
  50. zou/app/blueprints/index/resources.py +38 -29
  51. zou/app/blueprints/news/resources.py +25 -11
  52. zou/app/blueprints/persons/__init__.py +5 -2
  53. zou/app/blueprints/persons/resources.py +126 -120
  54. zou/app/blueprints/previews/__init__.py +18 -8
  55. zou/app/blueprints/previews/resources.py +569 -328
  56. zou/app/blueprints/projects/resources.py +1 -1
  57. zou/app/blueprints/search/resources.py +18 -6
  58. zou/app/blueprints/shots/__init__.py +5 -0
  59. zou/app/blueprints/shots/resources.py +134 -4
  60. zou/app/blueprints/source/__init__.py +6 -6
  61. zou/app/blueprints/source/csv/assets.py +10 -3
  62. zou/app/blueprints/source/csv/base.py +1 -1
  63. zou/app/blueprints/source/csv/edits.py +10 -3
  64. zou/app/blueprints/source/csv/shots.py +10 -3
  65. zou/app/blueprints/source/{edl.py → otio.py} +82 -41
  66. zou/app/blueprints/tasks/__init__.py +3 -2
  67. zou/app/blueprints/tasks/resources.py +83 -52
  68. zou/app/blueprints/user/__init__.py +9 -0
  69. zou/app/blueprints/user/resources.py +170 -12
  70. zou/app/config.py +10 -0
  71. zou/app/mixin.py +6 -5
  72. zou/app/models/attachment_file.py +10 -4
  73. zou/app/models/base.py +18 -13
  74. zou/app/models/build_job.py +7 -4
  75. zou/app/models/chat.py +44 -0
  76. zou/app/models/chat_message.py +37 -0
  77. zou/app/models/comment.py +1 -0
  78. zou/app/models/day_off.py +3 -0
  79. zou/app/models/entity.py +4 -6
  80. zou/app/models/entity_type.py +2 -0
  81. zou/app/models/organisation.py +14 -15
  82. zou/app/models/person.py +6 -1
  83. zou/app/models/project.py +3 -0
  84. zou/app/models/search_filter.py +11 -0
  85. zou/app/models/search_filter_group.py +10 -0
  86. zou/app/models/serializer.py +17 -17
  87. zou/app/models/status_automation.py +2 -0
  88. zou/app/models/studio.py +13 -0
  89. zou/app/models/subscription.py +2 -2
  90. zou/app/models/task.py +6 -1
  91. zou/app/models/task_status.py +1 -0
  92. zou/app/models/task_type.py +1 -0
  93. zou/app/models/working_file.py +1 -1
  94. zou/app/services/assets_service.py +101 -14
  95. zou/app/services/auth_service.py +17 -44
  96. zou/app/services/breakdown_service.py +37 -5
  97. zou/app/services/chats_service.py +279 -0
  98. zou/app/services/comments_service.py +110 -65
  99. zou/app/services/concepts_service.py +4 -12
  100. zou/app/services/deletion_service.py +43 -30
  101. zou/app/services/edits_service.py +5 -11
  102. zou/app/services/emails_service.py +4 -4
  103. zou/app/services/entities_service.py +17 -2
  104. zou/app/services/events_service.py +12 -4
  105. zou/app/services/exception.py +5 -5
  106. zou/app/services/names_service.py +7 -2
  107. zou/app/services/news_service.py +17 -9
  108. zou/app/services/persons_service.py +38 -21
  109. zou/app/services/playlists_service.py +8 -7
  110. zou/app/services/preview_files_service.py +137 -10
  111. zou/app/services/projects_service.py +5 -14
  112. zou/app/services/shots_service.py +221 -49
  113. zou/app/services/sync_service.py +46 -42
  114. zou/app/services/tasks_service.py +185 -46
  115. zou/app/services/time_spents_service.py +67 -20
  116. zou/app/services/user_service.py +350 -107
  117. zou/app/stores/auth_tokens_store.py +2 -1
  118. zou/app/stores/file_store.py +18 -0
  119. zou/app/stores/publisher_store.py +7 -7
  120. zou/app/stores/queue_store.py +1 -0
  121. zou/app/swagger.py +36 -20
  122. zou/app/utils/cache.py +2 -0
  123. zou/app/utils/commands.py +104 -7
  124. zou/app/utils/csv_utils.py +1 -4
  125. zou/app/utils/date_helpers.py +33 -17
  126. zou/app/utils/dbhelpers.py +14 -1
  127. zou/app/utils/emails.py +2 -2
  128. zou/app/utils/fido.py +22 -0
  129. zou/app/utils/query.py +54 -6
  130. zou/app/utils/redis.py +11 -0
  131. zou/app/utils/saml.py +51 -0
  132. zou/app/utils/string.py +2 -0
  133. zou/app/utils/thumbnail.py +4 -2
  134. zou/cli.py +76 -18
  135. zou/debug.py +4 -2
  136. zou/event_stream.py +122 -165
  137. zou/job_settings.py +1 -0
  138. zou/migrations/env.py +0 -0
  139. zou/migrations/utils/base.py +6 -6
  140. zou/migrations/versions/1bb55759146f_add_table_studio.py +67 -0
  141. zou/migrations/versions/1fab8c420678_add_attachments_to_message_chats.py +56 -0
  142. zou/migrations/versions/23122f290ca2_add_entity_chat_models.py +149 -0
  143. zou/migrations/versions/32f134ff1201_add_is_shared_flag_to_filters.py +33 -0
  144. zou/migrations/versions/57222395f2be_add_statusautomation_import_last_revision.py +41 -0
  145. zou/migrations/versions/59a7445a966c_add_entity_is_shared.py +41 -0
  146. zou/migrations/versions/5b980f0dc365_add_comment_links.py +35 -0
  147. zou/migrations/versions/680c64565f9d_for_searchfiltergroup_is_shared.py +35 -0
  148. zou/migrations/versions/8e67c183bed7_add_preference_fields.py +71 -0
  149. zou/migrations/versions/92b40d79ad3f_allow_message_attachments.py +38 -0
  150. zou/migrations/versions/971dbf5a0faf_add_short_name_for_asset_type_entity_.py +33 -0
  151. zou/migrations/versions/9b85c14fa8a7_add_day_off_new_columns.py +68 -0
  152. zou/migrations/versions/9d3bb33c6fc6_add_department_keys_to_filter_models.py +73 -0
  153. zou/migrations/versions/a252a094e977_add_descriptions_for_entities_tasks_and_.py +40 -0
  154. zou/migrations/versions/be56dc0fb760_for_is_shared_disallow_nullable.py +102 -0
  155. zou/migrations/versions/ca28796a2a62_add_is_done_field_to_the_task_model.py +108 -0
  156. zou/migrations/versions/f344b867a911_for_description_of_entity_task_working_.py +75 -0
  157. zou/remote/config_payload.py +2 -1
  158. zou/utils/movie.py +14 -4
  159. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/METADATA +75 -69
  160. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/RECORD +163 -134
  161. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/WHEEL +1 -1
  162. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/LICENSE +0 -0
  163. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/entry_points.txt +0 -0
  164. {zou-0.19.15.dist-info → zou-0.20.11.dist-info}/top_level.txt +0 -0
zou/app/utils/redis.py ADDED
@@ -0,0 +1,11 @@
1
+ from zou.app import config
2
+
3
+
4
+ def get_redis_url(db_index):
5
+ redis_host = config.KEY_VALUE_STORE["host"]
6
+ redis_port = config.KEY_VALUE_STORE["port"]
7
+ if config.KEY_VALUE_STORE["password"]:
8
+ redis_password = f":{config.KEY_VALUE_STORE['password']}@"
9
+ else:
10
+ redis_password = ""
11
+ return f"redis://{redis_password}{redis_host}:{redis_port}/{db_index}"
zou/app/utils/saml.py ADDED
@@ -0,0 +1,51 @@
1
+ import requests
2
+
3
+ from saml2 import (
4
+ BINDING_HTTP_POST,
5
+ BINDING_HTTP_REDIRECT,
6
+ )
7
+ from saml2.client import Saml2Client
8
+ from saml2.config import Config as Saml2Config
9
+
10
+ from zou.app import config
11
+
12
+
13
+ def saml_client_for(metadata_url):
14
+ """
15
+ Given the name of an IdP, return a configuation.
16
+ The configuration is a hash for use by saml2.config.Config
17
+ """
18
+ acs_url = (
19
+ f"{config.DOMAIN_PROTOCOL}://{config.DOMAIN_NAME}/api/auth/saml/sso"
20
+ )
21
+
22
+ rv = requests.get(metadata_url)
23
+
24
+ settings = {
25
+ "entityid": f"{config.DOMAIN_PROTOCOL}://{config.DOMAIN_NAME}/api/auth/saml/login",
26
+ "metadata": {"inline": [rv.text]},
27
+ "service": {
28
+ "sp": {
29
+ "endpoints": {
30
+ "assertion_consumer_service": [
31
+ (acs_url, BINDING_HTTP_REDIRECT),
32
+ (acs_url, BINDING_HTTP_POST),
33
+ ],
34
+ },
35
+ # Don't verify that the incoming requests originate from us via
36
+ # the built-in cache for authn request ids in pysaml2
37
+ "allow_unsolicited": True,
38
+ # Don't sign authn requests, since signed requests only make
39
+ # sense in a situation where you control both the SP and IdP
40
+ "authn_requests_signed": False,
41
+ "logout_requests_signed": True,
42
+ "want_assertions_signed": True,
43
+ "want_response_signed": False,
44
+ },
45
+ },
46
+ }
47
+ spConfig = Saml2Config()
48
+ spConfig.load(settings)
49
+ spConfig.allow_unknown_attributes = True
50
+ saml_client = Saml2Client(config=spConfig)
51
+ return saml_client
zou/app/utils/string.py CHANGED
@@ -6,6 +6,8 @@ def strtobool(val):
6
6
  """
7
7
  if isinstance(val, bool):
8
8
  return val
9
+ if isinstance(val, int):
10
+ return bool(val)
9
11
  elif val.lower() in ("y", "yes", "t", "true", "on", "1"):
10
12
  return True
11
13
  elif val.lower() in ("n", "no", "f", "false", "off", "0"):
@@ -76,7 +76,7 @@ def make_im_bigger_if_needed(im, size):
76
76
  return im
77
77
 
78
78
 
79
- def fit_to_target_size(im, size):
79
+ def fit_to_target_size(im, size, crop=False):
80
80
  """
81
81
  Make sure that the image is contained in the size given in parameter
82
82
  (shorten width and/or height proporitionnally to expected ratio).
@@ -131,11 +131,13 @@ def turn_into_thumbnail(file_path, size=None):
131
131
  return file_path
132
132
 
133
133
 
134
- def resize(file_path, size):
134
+ def resize(file_path, size, crop=False):
135
135
  """
136
136
  Resize given picture
137
137
  """
138
138
  im = Image.open(file_path)
139
+ if crop:
140
+ im = prepare_image_for_thumbnail(im, size)
139
141
  im = im.resize(size, Image.Resampling.LANCZOS)
140
142
  if im.mode == "CMYK":
141
143
  im = im.convert("RGB")
zou/cli.py CHANGED
@@ -30,7 +30,7 @@ def cli():
30
30
  @cli.command()
31
31
  def version():
32
32
  """
33
- Returns current installation version.
33
+ Return current installation version.
34
34
  """
35
35
  from zou import __version__
36
36
 
@@ -39,7 +39,7 @@ def version():
39
39
 
40
40
  @cli.command()
41
41
  def init_db():
42
- "Creates datababase table (database must be created through PG client)."
42
+ "Create database table (database must be created through PG client)."
43
43
 
44
44
  print("Creating database and tables...")
45
45
  with app.app_context():
@@ -47,6 +47,22 @@ def init_db():
47
47
  print("Database and tables created.")
48
48
 
49
49
 
50
+ @cli.command()
51
+ def is_db_ready():
52
+ """
53
+ Return a message telling whether the database is initialized or not.
54
+ """
55
+ with app.app_context():
56
+ is_init = dbhelpers.is_init()
57
+ if is_init:
58
+ print("Database is initialized.")
59
+ else:
60
+ print(
61
+ "Database is not initialized. "
62
+ "Run 'zou init-db' and 'zou init-data'."
63
+ )
64
+
65
+
50
66
  @cli.command()
51
67
  @click.option("--message", default="")
52
68
  def migrate_db(message):
@@ -82,7 +98,7 @@ def clear_db():
82
98
 
83
99
  @cli.command()
84
100
  def reset_db():
85
- "Drop all tables then recreates them."
101
+ "Drop all tables, then recreate them."
86
102
  with app.app_context():
87
103
  print("Deleting database and tables...")
88
104
  dbhelpers.drop_all()
@@ -96,7 +112,7 @@ def reset_db():
96
112
  @click.option("--no-telemetry", is_flag=True, default=False)
97
113
  def upgrade_db(no_telemetry=False):
98
114
  """
99
- Upgrade database schema. Send anonymized statistics to our telemery
115
+ Upgrade database schema. Send anonymized statistics to our telemetry
100
116
  services (user and preview amounts). It allows us to size the Kitsu
101
117
  community.
102
118
  """
@@ -140,7 +156,7 @@ def reset_migrations():
140
156
 
141
157
  @cli.command()
142
158
  @click.argument("email")
143
- @click.option("--password", required=True)
159
+ @click.option("--password", default=None)
144
160
  def create_admin(email, password):
145
161
  """
146
162
  Create an admin user to allow usage of the API when database is empty.
@@ -149,10 +165,18 @@ def create_admin(email, password):
149
165
  try:
150
166
  person = persons_service.get_person_by_email(email)
151
167
  if person["role"] != "admin":
152
- persons_service.update_person(person["id"], {"role": "admin"})
168
+ persons_service.update_person(
169
+ person["id"],
170
+ {"role": "admin"},
171
+ bypass_protected_accounts=True,
172
+ )
153
173
  print("Existing user's role has been upgraded to 'admin'.")
154
174
  except PersonNotFoundException:
155
175
  try:
176
+ if password is None:
177
+ raise click.MissingParameter(
178
+ param_type="option", param_hint="--password"
179
+ )
156
180
  auth.validate_password(password)
157
181
  # Allow "admin@example.com" to be invalid.
158
182
  if email != "admin@example.com":
@@ -192,7 +216,7 @@ def clear_all_auth_tokens():
192
216
 
193
217
  @cli.command()
194
218
  def init_data():
195
- "Generates minimal data set required to run Kitsu."
219
+ "Generate minimal data set required to run Kitsu."
196
220
  commands.init_data()
197
221
 
198
222
 
@@ -397,7 +421,7 @@ def sync_file_changes(event_source, source, logs_directory):
397
421
  @click.option("--source", default="http://localhost:8080/api")
398
422
  @click.option("--minutes", default=0)
399
423
  @click.option("--page-size", default=300)
400
- def sync_last_events(source, minutes, page_size):
424
+ def sync_last_events(source, minutes, limit):
401
425
  """
402
426
  Retrieve last events that occured on source instance and import data related
403
427
  to them. It expects that credentials to connect to source instance are
@@ -406,7 +430,7 @@ def sync_last_events(source, minutes, page_size):
406
430
  login = os.getenv("SYNC_LOGIN")
407
431
  password = os.getenv("SYNC_PASSWORD")
408
432
  commands.import_last_changes_from_another_instance(
409
- source, login, password, minutes=minutes, page_size=page_size
433
+ source, login, password, minutes=minutes, limit=limit
410
434
  )
411
435
 
412
436
 
@@ -414,16 +438,16 @@ def sync_last_events(source, minutes, page_size):
414
438
  @click.option("--source", default="http://localhost:8080/api")
415
439
  @click.option("--minutes", default=20)
416
440
  @click.option("--page-size", default=50)
417
- def sync_last_files(source, minutes, page_size):
441
+ def sync_last_files(source, minutes, limit):
418
442
  """
419
- Retrieve last preview files and thumbnails updloaded on source instance.
443
+ Retrieve last preview files and thumbnails uploaded on source instance.
420
444
  It expects that credentials to connect to source instance are
421
445
  given through SYNC_LOGIN and SYNC_PASSWORD environment variables.
422
446
  """
423
447
  login = os.getenv("SYNC_LOGIN")
424
448
  password = os.getenv("SYNC_PASSWORD")
425
449
  commands.import_last_file_changes_from_another_instance(
426
- source, login, password, minutes=minutes, page_size=page_size
450
+ source, login, password, minutes=minutes, limit=limit
427
451
  )
428
452
 
429
453
 
@@ -456,13 +480,13 @@ def upload_files_to_cloud_storage(days):
456
480
 
457
481
 
458
482
  @cli.command()
459
- @click.option("--projectid")
460
- def clean_tasks_data(projectid):
483
+ @click.option("--project-id")
484
+ def clean_tasks_data(project_id):
461
485
  """
462
486
  Reset task models data (retake count, wip start date and end date)
463
487
  """
464
- if projectid is not None:
465
- commands.reset_tasks_data(projectid)
488
+ if project_id is not None:
489
+ commands.reset_tasks_data(project_id)
466
490
 
467
491
 
468
492
  @cli.command()
@@ -470,7 +494,7 @@ def clean_tasks_data(projectid):
470
494
  def remove_old_data(days):
471
495
  """
472
496
  Remove old events, notifications and login logs older than 90 days
473
- (by deafult).
497
+ (by default).
474
498
  """
475
499
  commands.remove_old_data(days)
476
500
 
@@ -569,7 +593,7 @@ def reset_breakdown_data():
569
593
  @click.option("--email", required=True)
570
594
  @click.option("--name", required=True)
571
595
  @click.option(
572
- "--expiration_date",
596
+ "--expiration-date",
573
597
  required=False,
574
598
  default=None,
575
599
  show_default=True,
@@ -593,5 +617,39 @@ def create_bot(
593
617
  )
594
618
 
595
619
 
620
+ @cli.command()
621
+ @click.option(
622
+ "--preview-file-id",
623
+ required=False,
624
+ default=None,
625
+ show_default=True,
626
+ )
627
+ @click.option(
628
+ "--project-id",
629
+ required=False,
630
+ default=None,
631
+ show_default=True,
632
+ )
633
+ @click.option("--all-broken", is_flag=True, default=False, show_default=True)
634
+ @click.option(
635
+ "--all-processing", is_flag=True, default=False, show_default=True
636
+ )
637
+ def renormalize_movie_preview_files(
638
+ preview_file_id,
639
+ project_id,
640
+ all_broken,
641
+ all_processing,
642
+ ):
643
+ """
644
+ Renormalize all preview files.
645
+ """
646
+ commands.renormalize_movie_preview_files(
647
+ preview_file_id,
648
+ project_id,
649
+ all_broken,
650
+ all_processing,
651
+ )
652
+
653
+
596
654
  if __name__ == "__main__":
597
655
  cli()
zou/debug.py CHANGED
@@ -13,6 +13,8 @@ socketio = SocketIO(app, cors_allowed_origins=[], cors_credentials=False)
13
13
 
14
14
  if __name__ == "__main__":
15
15
  print(
16
- "The Kitsu API server is listening on port %s..." % config.DEBUG_PORT
16
+ f"The Kitsu API server is listening on http://{config.DEBUG_HOST}:{config.DEBUG_PORT}"
17
+ )
18
+ socketio.run(
19
+ app, host=config.DEBUG_HOST, port=config.DEBUG_PORT, debug=True
17
20
  )
18
- socketio.run(app, port=config.DEBUG_PORT, debug=True)
zou/event_stream.py CHANGED
@@ -2,25 +2,25 @@ from gevent import monkey
2
2
 
3
3
  monkey.patch_all()
4
4
 
5
- from flask import Flask, jsonify
5
+ from flask import jsonify
6
6
  from flask_jwt_extended import (
7
7
  get_jwt_identity,
8
8
  jwt_required,
9
9
  verify_jwt_in_request,
10
- JWTManager,
11
10
  )
12
11
  from flask_socketio import SocketIO, disconnect, join_room, emit
13
- from flask_sqlalchemy import SQLAlchemy
14
- from zou.app import config
15
- from zou.app.stores import auth_tokens_store
16
- from zou.app.utils.monitoring import init_monitoring
17
- from zou.app.utils.flask import ORJSONProvider
18
- from zou.app.services import persons_service
12
+
13
+ from zou.app import config, app
14
+ from zou.app.utils.redis import get_redis_url
19
15
 
20
16
  server_stats = {"nb_connections": 0}
21
17
  rooms_data = {}
22
18
 
23
- # Review room helpers
19
+ redis_url = get_redis_url(config.KV_EVENTS_DB_INDEX)
20
+ socketio = SocketIO(
21
+ logger=True, cors_allowed_origins=[], cors_credentials=False
22
+ )
23
+ socketio.init_app(app, message_queue=redis_url, async_mode="gevent")
24
24
 
25
25
 
26
26
  def _get_empty_room(current_frame=0):
@@ -78,174 +78,131 @@ def _update_room_playing_status(data, room):
78
78
  return room
79
79
 
80
80
 
81
- # Database helpers
81
+ @app.route("/", methods=["GET"])
82
+ def index():
83
+ return jsonify({"name": "%s Event stream" % config.APP_NAME})
82
84
 
83
85
 
84
- def get_redis_url():
85
- redis_host = config.KEY_VALUE_STORE["host"]
86
- redis_port = config.KEY_VALUE_STORE["port"]
87
- db_index = config.KV_EVENTS_DB_INDEX
88
- return "redis://%s:%s/%s" % (redis_host, redis_port, db_index)
86
+ @app.route("/stats", methods=["GET"])
87
+ def stats():
88
+ return jsonify(server_stats)
89
89
 
90
90
 
91
- # Routes
91
+ @socketio.on("connect", namespace="/events")
92
+ def connected(_):
93
+ try:
94
+ verify_jwt_in_request()
95
+ server_stats["nb_connections"] += 1
96
+ app.logger.info("New websocket client connected")
97
+ except Exception:
98
+ app.logger.info("New websocket client failed to connect")
99
+ disconnect()
100
+ return False
92
101
 
93
102
 
94
- def set_info_routes(socketio, app):
95
- @app.route("/", methods=["GET"])
96
- def index():
97
- return jsonify({"name": "%s Event stream" % config.APP_NAME})
103
+ @socketio.on("disconnect", namespace="/events")
104
+ def disconnected(_):
105
+ try:
106
+ verify_jwt_in_request()
107
+ user_id = get_jwt_identity()
108
+ # needed to be able to clear empty rooms
109
+ tmp_rooms_data = dict(rooms_data)
110
+ for room_id in tmp_rooms_data:
111
+ _leave_room(room_id, user_id)
112
+ server_stats["nb_connections"] -= 1
113
+ app.logger.info("Websocket client disconnected")
114
+ except Exception:
115
+ pass
116
+
117
+
118
+ @socketio.on_error("/events")
119
+ def on_error(error):
120
+ server_stats["nb_connections"] -= 1
121
+ if server_stats["nb_connections"] < 0:
122
+ server_stats["nb_connections"] = 0
123
+ app.logger.error(error)
124
+
125
+
126
+ @app.route("/rooms", methods=["GET", "POST"])
127
+ @jwt_required()
128
+ def rooms():
129
+ return jsonify({"name": "%s Review rooms" % config.APP_NAME})
130
+
131
+
132
+ @socketio.on("preview-room:open-playlist", namespace="/events")
133
+ @jwt_required()
134
+ def on_open_playlist(data):
135
+ """
136
+ when a person opens the playlist page he immediately enters the
137
+ websocket room. This way he can see in live which people are in the
138
+ review room. The user still has to explicitly enter the review room
139
+ to actually be in sync with the other users
140
+ """
141
+ room, room_id = _get_room_from_data(data)
142
+ rooms_data[room_id] = room
143
+ join_room(room_id)
144
+ emit("preview-room:room-people-updated", room, room=room_id)
98
145
 
99
- @app.route("/stats", methods=["GET"])
100
- def stats():
101
- return jsonify(server_stats)
102
146
 
147
+ @socketio.on("preview-room:join", namespace="/events")
148
+ @jwt_required()
149
+ def on_join(data):
150
+ """
151
+ When a person joins the review room, we notify all its members that a
152
+ new person is added to the room.
153
+ """
154
+ user_id = get_jwt_identity()
155
+ room, room_id = _get_room_from_data(data)
156
+ if len(room["people"]) == 0:
157
+ _update_room_playing_status(data, room)
158
+ room["people"] = list(set(room["people"] + [user_id]))
159
+ rooms_data[room_id] = room
160
+ emit("preview-room:room-people-updated", room, room=room_id)
103
161
 
104
- def set_application_routes(socketio, app):
105
- @socketio.on("connect", namespace="/events")
106
- def connected():
107
- try:
108
- verify_jwt_in_request()
109
- server_stats["nb_connections"] += 1
110
- app.logger.info("New websocket client connected")
111
- except Exception:
112
- app.logger.info("New websocket client failed to connect")
113
- disconnect()
114
- return False
115
162
 
116
- @socketio.on("disconnect", namespace="/events")
117
- def disconnected():
118
- try:
119
- verify_jwt_in_request()
120
- user_id = get_jwt_identity()
121
- # needed to be able to clear empty rooms
122
- tmp_rooms_data = dict(rooms_data)
123
- for room_id in tmp_rooms_data:
124
- _leave_room(room_id, user_id)
125
- server_stats["nb_connections"] -= 1
126
- app.logger.info("Websocket client disconnected")
127
- except Exception:
128
- pass
163
+ @socketio.on("preview-room:leave", namespace="/events")
164
+ @jwt_required()
165
+ def on_leave(data):
166
+ user_id = get_jwt_identity()
167
+ room_id = data["playlist_id"]
168
+ _leave_room(room_id, user_id)
129
169
 
130
- @socketio.on_error("/events")
131
- def on_error(error):
132
- server_stats["nb_connections"] -= 1
133
- if server_stats["nb_connections"] < 0:
134
- server_stats["nb_connections"] = 0
135
- app.logger.error(error)
136
-
137
-
138
- def set_playlist_room_routes(socketio, app):
139
- @app.route("/rooms", methods=["GET", "POST"])
140
- @jwt_required()
141
- def rooms():
142
- return jsonify({"name": "%s Review rooms" % config.APP_NAME})
143
-
144
- @socketio.on("preview-room:open-playlist", namespace="/events")
145
- @jwt_required()
146
- def on_open_playlist(data):
147
- """
148
- when a person opens the playlist page he immediately enters the
149
- websocket room. This way he can see in live which people are in the
150
- review room. The user still has to explicitly enter the review room
151
- to actually be in sync with the other users
152
- """
153
- room, room_id = _get_room_from_data(data)
154
- rooms_data[room_id] = room
155
- join_room(room_id)
156
- emit("preview-room:room-people-updated", room, room=room_id)
157
-
158
- @socketio.on("preview-room:join", namespace="/events")
159
- @jwt_required()
160
- def on_join(data):
161
- """
162
- When a person joins the review room, we notify all its members that a
163
- new person is added to the room.
164
- """
165
- user_id = get_jwt_identity()
166
- room, room_id = _get_room_from_data(data)
167
- if len(room["people"]) == 0:
168
- _update_room_playing_status(data, room)
169
- room["people"] = list(set(room["people"] + [user_id]))
170
- rooms_data[room_id] = room
171
- emit("preview-room:room-people-updated", room, room=room_id)
172
170
 
173
- @socketio.on("preview-room:leave", namespace="/events")
174
- @jwt_required()
175
- def on_leave(data):
176
- user_id = get_jwt_identity()
177
- room_id = data["playlist_id"]
178
- _leave_room(room_id, user_id)
179
-
180
- @socketio.on("preview-room:update-playing-status", namespace="/events")
181
- @jwt_required()
182
- def on_playing_status_updated(data, only_newcomer=False):
183
- room, room_id = _get_room_from_data(data)
184
- rooms_data[room_id] = _update_room_playing_status(data, room)
185
- event_data = {"only_newcomer": only_newcomer, **rooms_data[room_id]}
186
- emit("preview-room:room-updated", event_data, room=room_id)
187
-
188
- @socketio.on("preview-room:add-annotation", namespace="/events")
189
- @jwt_required()
190
- def on_add_annotation(data):
191
- room_id = data["playlist_id"]
192
- emit("preview-room:add-annotation", data, room=room_id)
193
-
194
- @socketio.on("preview-room:remove-annotation", namespace="/events")
195
- @jwt_required()
196
- def on_remove_annotation(data):
197
- room_id = data["playlist_id"]
198
- emit("preview-room:remove-annotation", data, room=room_id)
199
-
200
- @socketio.on("preview-room:update-annotation", namespace="/events")
201
- @jwt_required()
202
- def on_update_annotation(data):
203
- room_id = data["playlist_id"]
204
- emit("preview-room:update-annotation", data, room=room_id)
205
-
206
- @socketio.on("preview-room:change-version", namespace="/events")
207
- @jwt_required()
208
- def on_change_version(data):
209
- room_id = data["playlist_id"]
210
- emit("preview-room:change-version", data, room=room_id)
211
-
212
- return app
213
-
214
-
215
- def create_app():
216
- redis_url = get_redis_url()
217
- socketio = SocketIO(
218
- logger=True, cors_allowed_origins=[], cors_credentials=False
219
- )
220
- app = Flask(__name__)
221
- app.json = ORJSONProvider(app)
222
- app.config.from_object(config)
223
- init_monitoring(app)
224
- db = SQLAlchemy(app)
225
- app.extensions["sqlalchemy"].db = db
226
- set_info_routes(socketio, app)
227
- set_application_routes(socketio, app)
228
- set_playlist_room_routes(socketio, app)
229
- socketio.init_app(app, message_queue=redis_url, async_mode="gevent")
230
- return (app, socketio)
231
-
232
-
233
- def set_auth(app):
234
- jwt = JWTManager(app) # JWT auth tokens
235
-
236
- @jwt.token_in_blocklist_loader
237
- def check_if_token_is_revoked(_, payload):
238
- identity_type = payload.get("identity_type")
239
- if identity_type == "person":
240
- return auth_tokens_store.is_revoked(payload["jti"])
241
- elif identity_type in ["bot", "person_api"]:
242
- return persons_service.is_jti_revoked(payload["jti"])
243
- else:
244
- return True
245
-
246
-
247
- (app, socketio) = create_app()
248
- set_auth(app)
171
+ @socketio.on("preview-room:update-playing-status", namespace="/events")
172
+ @jwt_required()
173
+ def on_playing_status_updated(data, only_newcomer=False):
174
+ room, room_id = _get_room_from_data(data)
175
+ rooms_data[room_id] = _update_room_playing_status(data, room)
176
+ event_data = {"only_newcomer": only_newcomer, **rooms_data[room_id]}
177
+ emit("preview-room:room-updated", event_data, room=room_id)
178
+
179
+
180
+ @socketio.on("preview-room:add-annotation", namespace="/events")
181
+ @jwt_required()
182
+ def on_add_annotation(data):
183
+ room_id = data["playlist_id"]
184
+ emit("preview-room:add-annotation", data, room=room_id)
185
+
186
+
187
+ @socketio.on("preview-room:remove-annotation", namespace="/events")
188
+ @jwt_required()
189
+ def on_remove_annotation(data):
190
+ room_id = data["playlist_id"]
191
+ emit("preview-room:remove-annotation", data, room=room_id)
192
+
193
+
194
+ @socketio.on("preview-room:update-annotation", namespace="/events")
195
+ @jwt_required()
196
+ def on_update_annotation(data):
197
+ room_id = data["playlist_id"]
198
+ emit("preview-room:update-annotation", data, room=room_id)
199
+
200
+
201
+ @socketio.on("preview-room:change-version", namespace="/events")
202
+ @jwt_required()
203
+ def on_change_version(data):
204
+ room_id = data["playlist_id"]
205
+ emit("preview-room:change-version", data, room=room_id)
249
206
 
250
207
 
251
208
  if __name__ == "__main__":
zou/job_settings.py CHANGED
@@ -3,3 +3,4 @@ from zou.app import config
3
3
  REDIS_HOST = config.KEY_VALUE_STORE["host"]
4
4
  REDIS_PORT = config.KEY_VALUE_STORE["port"]
5
5
  REDIS_DB = config.KV_JOB_DB_INDEX
6
+ REDIS_PASSWORD = config.KEY_VALUE_STORE["password"]
zou/migrations/env.py CHANGED
File without changes
@@ -1,7 +1,5 @@
1
- import datetime
2
-
3
1
  from sqlalchemy_utils import UUIDType
4
- from zou.app.utils import fields
2
+ from zou.app.utils import fields, date_helpers
5
3
  import sqlalchemy as sa
6
4
 
7
5
 
@@ -11,9 +9,11 @@ class BaseMixin(object):
11
9
  )
12
10
 
13
11
  # Audit fields
14
- created_at = sa.Column(sa.DateTime, default=datetime.datetime.utcnow)
12
+ created_at = sa.Column(
13
+ sa.DateTime, default=date_helpers.get_utc_now_datetime
14
+ )
15
15
  updated_at = sa.Column(
16
16
  sa.DateTime,
17
- default=datetime.datetime.utcnow,
18
- onupdate=datetime.datetime.utcnow,
17
+ default=date_helpers.get_utc_now_datetime,
18
+ onupdate=date_helpers.get_utc_now_datetime,
19
19
  )