diff --git a/.pkgr.yml b/.pkgr.yml
index 75fb97edf65..5c2df160750 100644
--- a/.pkgr.yml
+++ b/.pkgr.yml
@@ -41,7 +41,7 @@ installer: https://github.com/pkgr/installer.git
wizards:
- https://github.com/pkgr/addon-legacy-installer.git
- ./packaging/addons/openproject-edition
- - https://github.com/opf/addon-postgres#pg13
+ - https://github.com/pkgr/addon-postgres
- https://github.com/pkgr/addon-apache2.git
- ./packaging/addons/repositories
- https://github.com/pkgr/addon-smtp.git
diff --git a/app/contracts/attachments/prepare_upload_contract.rb b/app/contracts/attachments/prepare_upload_contract.rb
index ac319638cfd..fee24e51566 100644
--- a/app/contracts/attachments/prepare_upload_contract.rb
+++ b/app/contracts/attachments/prepare_upload_contract.rb
@@ -37,5 +37,24 @@ module Attachments
def validate_direct_uploads_active
errors.add :base, :not_available unless OpenProject::Configuration.direct_uploads?
end
+
+ ##
+ # The browser hasn't given a specific content type.
+ # So we don't check the content type here during the prepare_upload step yet.
+ #
+ # We'll do it again later in the FinishDirectUploadJob where the normal create contract
+ # without this opt-out is used, and where a more specific content type may be
+ # determined.
+ def validate_content_type
+ return if pending_content_type?
+
+ super
+ end
+
+ def pending_content_type?
+ return false unless OpenProject::Configuration.direct_uploads?
+
+ model.content_type == OpenProject::ContentTypeDetector::SENSIBLE_DEFAULT
+ end
end
end
diff --git a/app/models/group_user.rb b/app/models/group_user.rb
index c339c3abb36..96b9bc8c1ee 100644
--- a/app/models/group_user.rb
+++ b/app/models/group_user.rb
@@ -32,4 +32,7 @@ class GroupUser < ApplicationRecord
belongs_to :group,
touch: true
belongs_to :user
+
+ validates_presence_of :group
+ validates_presence_of :user
end
diff --git a/app/services/attachments/set_prepared_attributes_service.rb b/app/services/attachments/set_prepared_attributes_service.rb
index 9b804529897..ea291835620 100644
--- a/app/services/attachments/set_prepared_attributes_service.rb
+++ b/app/services/attachments/set_prepared_attributes_service.rb
@@ -50,9 +50,9 @@ module Attachments
model.extend(OpenProject::ChangedBySystem)
model.change_by_system do
model.downloads = -1
- # Set a content type as the file is not present
- # The real content type will be set by FinishDirectUploadJob
- model.content_type = params[:content_type] || OpenProject::ContentTypeDetector::SENSIBLE_DEFAULT
+ # Set a preliminary content type as the file is not present
+ # The content type will be updated by the FinishDirectUploadJob if necessary.
+ model.content_type = params[:content_type].presence || OpenProject::ContentTypeDetector::SENSIBLE_DEFAULT
end
end
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 760a5efa6d0..0cfd31995de 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -32,7 +32,7 @@ class Groups::UpdateService < ::BaseServices::Update
protected
def persist(call)
- removed_users = call.result.group_users.select(&:marked_for_destruction?).map(&:user)
+ removed_users = groups_removed_users(call.result)
member_roles = member_roles_to_prune(removed_users)
project_ids = member_roles.pluck(:project_id)
member_role_ids = member_roles.pluck(:id)
@@ -59,6 +59,10 @@ class Groups::UpdateService < ::BaseServices::Update
call
end
+ def groups_removed_users(group)
+ group.group_users.select(&:marked_for_destruction?).map(&:user).compact
+ end
+
def remove_member_roles(member_role_ids)
::Groups::CleanupInheritedRolesService
.new(model, current_user: user)
@@ -66,6 +70,8 @@ class Groups::UpdateService < ::BaseServices::Update
end
def member_roles_to_prune(users)
+ return MemberRole.none if users.empty?
+
MemberRole
.includes(member: :member_roles)
.where(inherited_from: model.members.joins(:member_roles).select('member_roles.id'))
diff --git a/app/workers/attachments/finish_direct_upload_job.rb b/app/workers/attachments/finish_direct_upload_job.rb
index 797c1b305f1..bf9a78f7f66 100644
--- a/app/workers/attachments/finish_direct_upload_job.rb
+++ b/app/workers/attachments/finish_direct_upload_job.rb
@@ -42,31 +42,53 @@ class Attachments::FinishDirectUploadJob < ApplicationJob
return Rails.logger.error("File for attachment #{attachment_id} was not uploaded.")
end
- begin
- set_attributes_from_file(attachment, local_file)
- save_attachment(attachment)
- journalize_container(attachment)
- attachment_created_event(attachment)
- ensure
- File.unlink(local_file.path) if File.exist?(local_file.path)
+ User.execute_as(attachment.author) do
+ attach_uploaded_file(attachment, local_file)
end
end
private
+ def attach_uploaded_file(attachment, local_file)
+ set_attributes_from_file(attachment, local_file)
+ validate_attachment(attachment)
+ save_attachment(attachment)
+ journalize_container(attachment)
+ attachment_created_event(attachment)
+ rescue StandardError => e
+ ::OpenProject.logger.error e
+ attachment.destroy
+ ensure
+ File.unlink(local_file.path) if File.exist?(local_file.path)
+ end
+
def set_attributes_from_file(attachment, local_file)
- attachment.downloads = 0
- attachment.set_file_size local_file
- attachment.set_content_type local_file
- attachment.set_digest local_file
+ attachment.extend(OpenProject::ChangedBySystem)
+ attachment.change_by_system do
+ attachment.downloads = 0
+ attachment.set_file_size local_file
+ attachment.set_content_type local_file
+ attachment.set_digest local_file
+ end
end
def save_attachment(attachment)
- User.execute_as(attachment.author) do
- attachment.save! if attachment.changed?
+ attachment.save! if attachment.changed?
+ end
+
+ def validate_attachment(attachment)
+ contract = create_contract attachment
+
+ unless contract.valid?
+ errors = contract.errors.full_messages.join(", ")
+ raise "Failed to validate attachment #{attachment.id}: #{errors}"
end
end
+ def create_contract(attachment)
+ ::Attachments::CreateContract.new(attachment, attachment.author)
+ end
+
def journalize_container(attachment)
journable = attachment.container
diff --git a/config/locales/crowdin/js-ar.yml b/config/locales/crowdin/js-ar.yml
index e9d9567fee0..9a86ff17ece 100644
--- a/config/locales/crowdin/js-ar.yml
+++ b/config/locales/crowdin/js-ar.yml
@@ -290,7 +290,7 @@ ar:
current_new_feature_html: >
The release contains various new features and improvements: