Files
gitlab-foss/lib/api/markdown_uploads.rb
2025-06-25 15:10:05 +00:00

294 lines
9.5 KiB
Ruby

# frozen_string_literal: true
module API
class MarkdownUploads < ::API::Base
include PaginationParams
feature_category :team_planning
FILENAME_QUERY_PARAM_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
filename: API::NO_SLASH_URL_PART_REGEX
)
before { authenticate_non_get! }
helpers do
def find_uploads(parent)
uploads = Banzai::UploadsFinder.new(parent: parent).execute
uploads.preload_uploaded_by_user
end
def find_upload(parent, upload_id: nil, secret: nil, filename: nil)
finder = Banzai::UploadsFinder.new(parent: parent)
if upload_id
finder.find(upload_id)
else
finder.find_by_secret_and_filename(secret, filename)
end
end
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Workhorse authorize the file upload' do
detail 'This feature was introduced in GitLab 13.11'
success code: 200
failure [
{ code: 404, message: 'Not found' }
]
tags %w[projects]
end
post ':id/uploads/authorize' do
require_gitlab_workhorse!
status 200
content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
FileUploader.workhorse_authorize(has_length: false, maximum_size: user_project.max_attachment_size)
end
desc 'Upload a file' do
success code: 201, model: Entities::ProjectUpload
failure [
{ code: 404, message: 'Not found' }
]
tags %w[projects]
end
params do
requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile],
desc: 'The attachment file to be uploaded', documentation: { type: 'file' }
end
post ':id/uploads' do
upload = UploadService.new(user_project, params[:file], uploaded_by_user_id: current_user&.id).execute
present upload, with: Entities::ProjectUpload
end
desc 'Get the list of uploads of a project' do
success code: 200, model: Entities::MarkdownUploadAdmin
failure [
{ code: 403, message: 'Unauthenticated' },
{ code: 404, message: 'Not found' }
]
is_array true
tags %w[projects]
end
params do
use :pagination
end
get ':id/uploads' do
authorize! :admin_upload, user_project
uploads = find_uploads(user_project)
present paginate(uploads), with: Entities::MarkdownUploadAdmin
end
desc 'Download a single project upload by ID' do
success File
failure [
{ code: 403, message: 'Unauthenticated' },
{ code: 404, message: 'Not found' }
]
tags %w[projects]
end
params do
requires :upload_id, type: Integer, desc: 'The ID of a project upload'
end
get ':id/uploads/:upload_id' do
# Fetching uploads by ID is maintainer-only because it can be used to enumerate uploads
# even without the secret
authorize! :admin_upload, user_project
upload = find_upload(user_project, upload_id: params[:upload_id])
present_carrierwave_file!(upload.retrieve_uploader)
end
desc 'Download a single project upload by secret and filename' do
success File
failure [
{ code: 403, message: 'Unauthenticated' },
{ code: 404, message: 'Not found' }
]
tags %w[projects]
end
params do
requires :secret, type: String, desc: 'The 32-character secret of a project upload'
requires :filename, type: String, file_path: true, desc: 'The filename of a project upload'
end
get ':id/uploads/:secret/:filename', requirements: FILENAME_QUERY_PARAM_REQUIREMENTS do
authorize! :read_upload, user_project
upload = find_upload(user_project, secret: params[:secret], filename: params[:filename])
present_carrierwave_file!(upload&.retrieve_uploader)
end
desc 'Delete a single project upload by ID' do
success code: 204
failure [
{ code: 400, message: 'Bad request' },
{ code: 403, message: 'Unauthenticated' },
{ code: 404, message: 'Not found' }
]
tags %w[projects]
end
params do
requires :upload_id, type: Integer, desc: 'The ID of a project upload'
end
delete ':id/uploads/:upload_id' do
authorize! :destroy_upload, user_project
upload = find_upload(user_project, upload_id: params[:upload_id])
result = Uploads::DestroyService.new(user_project, current_user).execute(upload)
if result[:status] == :success
status 204
else
bad_request!(result[:message])
end
end
desc 'Delete a single project upload by secret and filename' do
success code: 204
failure [
{ code: 400, message: 'Bad request' },
{ code: 403, message: 'Unauthenticated' },
{ code: 404, message: 'Not found' }
]
tags %w[projects]
end
params do
requires :secret, type: String, desc: 'The 32-character secret of a project upload'
requires :filename, type: String, file_path: true, desc: 'The filename of a project upload'
end
delete ':id/uploads/:secret/:filename', requirements: FILENAME_QUERY_PARAM_REQUIREMENTS do
authorize! :destroy_upload, user_project
upload = find_upload(user_project, secret: params[:secret], filename: params[:filename])
result = Uploads::DestroyService.new(user_project, current_user).execute(upload)
if result[:status] == :success
status 204
else
bad_request!(result[:message])
end
end
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get the list of uploads of a group' do
success code: 200, model: Entities::MarkdownUploadAdmin
failure [
{ code: 403, message: 'Unauthenticated' },
{ code: 404, message: 'Not found' }
]
is_array true
tags %w[groups]
end
params do
use :pagination
end
get ':id/uploads' do
authorize! :admin_upload, user_group
uploads = find_uploads(user_group)
present paginate(uploads), with: Entities::MarkdownUploadAdmin
end
desc 'Download a single group upload by ID' do
success File
failure [
{ code: 403, message: 'Unauthenticated' },
{ code: 404, message: 'Not found' }
]
tags %w[groups]
end
params do
requires :upload_id, type: Integer, desc: 'The ID of a group upload'
end
get ':id/uploads/:upload_id' do
# Fetching uploads by ID is maintainer-only because it can be used to enumerate uploads
# even without the secret
authorize! :admin_upload, user_group
upload = find_upload(user_group, upload_id: params[:upload_id])
present_carrierwave_file!(upload.retrieve_uploader)
end
desc 'Download a single project upload by secret and filename' do
success File
failure [
{ code: 403, message: 'Unauthenticated' },
{ code: 404, message: 'Not found' }
]
tags %w[groups]
end
params do
requires :secret, type: String, desc: 'The 32-character secret of a group upload'
requires :filename, type: String, file_path: true, desc: 'The filename of a group upload'
end
get ':id/uploads/:secret/:filename', requirements: FILENAME_QUERY_PARAM_REQUIREMENTS do
authorize! :read_upload, user_group
upload = find_upload(user_group, secret: params[:secret], filename: params[:filename])
present_carrierwave_file!(upload&.retrieve_uploader)
end
desc 'Delete a single group upload' do
success code: 204
failure [
{ code: 400, message: 'Bad request' },
{ code: 403, message: 'Unauthenticated' },
{ code: 404, message: 'Not found' }
]
tags %w[groups]
end
params do
requires :upload_id, type: Integer, desc: 'The ID of a group upload'
end
delete ':id/uploads/:upload_id' do
authorize! :destroy_upload, user_group
upload = find_upload(user_group, upload_id: params[:upload_id])
result = Uploads::DestroyService.new(user_group, current_user).execute(upload)
if result[:status] == :success
status 204
else
bad_request!(result[:message])
end
end
desc 'Delete a single group upload by secret and filename' do
success code: 204
failure [
{ code: 400, message: 'Bad request' },
{ code: 403, message: 'Unauthenticated' },
{ code: 404, message: 'Not found' }
]
tags %w[groups]
end
params do
requires :secret, type: String, desc: 'The 32-character secret of a group upload'
requires :filename, type: String, file_path: true, desc: 'The filename of a group upload'
end
delete ':id/uploads/:secret/:filename', requirements: FILENAME_QUERY_PARAM_REQUIREMENTS do
authorize! :destroy_upload, user_group
upload = find_upload(user_group, secret: params[:secret], filename: params[:filename])
result = Uploads::DestroyService.new(user_group, current_user).execute(upload)
if result[:status] == :success
status 204
else
bad_request!(result[:message])
end
end
end
end
end