cdn/app/controllers/api/v4/uploads_controller.rb
End Nightshade 53f80ef244
batchy yay
2026-02-10 11:14:41 -07:00

157 lines
5.1 KiB
Ruby

# frozen_string_literal: true
module API
module V4
class UploadsController < ApplicationController
before_action :check_quota, only: [ :create, :create_from_url ]
# POST /api/v4/upload
def create
file = params[:file]
unless file.present?
render json: { error: "Missing file parameter" }, status: :bad_request
return
end
content_type = Marcel::MimeType.for(file.tempfile, name: file.original_filename) || file.content_type || "application/octet-stream"
upload_id = SecureRandom.uuid_v7
sanitized_filename = ActiveStorage::Filename.new(file.original_filename).sanitized
storage_key = "#{upload_id}/#{sanitized_filename}"
blob = ActiveStorage::Blob.create_and_upload!(
io: file.tempfile,
filename: file.original_filename,
content_type: content_type,
key: storage_key
)
upload = current_user.uploads.create!(id: upload_id, blob: blob, provenance: :api)
render json: upload_json(upload), status: :created
rescue => e
render json: { error: "Upload failed: #{e.message}" }, status: :unprocessable_entity
end
# POST /api/v4/uploads (batch)
def create_batch
files = params[:files]
unless files.present? && files.is_a?(Array)
render json: { error: "Missing files[] parameter" }, status: :bad_request
return
end
if files.size > BatchUploadService::MAX_FILES_PER_BATCH
render json: {
error: "Too many files",
detail: "Maximum #{BatchUploadService::MAX_FILES_PER_BATCH} files per batch, got #{files.size}"
}, status: :bad_request
return
end
service = BatchUploadService.new(user: current_user, provenance: :api)
result = service.process_files(files)
response = {
uploads: result.uploads.map { |u| upload_json(u) },
failed: result.failed.map { |f| { filename: f.filename, reason: f.reason } }
}
status = result.uploads.any? ? :created : :unprocessable_entity
render json: response, status: status
end
# POST /api/v4/upload_from_url
def create_from_url
url = params[:url]
unless url.present?
render json: { error: "Missing url parameter" }, status: :bad_request
return
end
download_auth = request.headers["X-Download-Authorization"]
upload = Upload.create_from_url(url, user: current_user, provenance: :api, original_url: url, authorization: download_auth)
quota_service = QuotaService.new(current_user)
unless quota_service.can_upload?(0)
if current_user.total_storage_bytes > quota_service.current_policy.max_total_storage
upload.destroy!
usage = quota_service.current_usage
render json: quota_error_json(usage), status: :payment_required
return
end
end
render json: upload_json(upload), status: :created
rescue => e
render json: { error: "Upload failed: #{e.message}" }, status: :unprocessable_entity
end
# PATCH /api/v4/uploads/:id/rename
def rename
upload = current_user.uploads.find(params[:id])
new_filename = params[:filename].to_s.strip
if new_filename.blank?
render json: { error: "Missing filename parameter" }, status: :bad_request
return
end
upload.rename!(new_filename)
render json: upload_json(upload)
rescue ActiveRecord::RecordNotFound
render json: { error: "Upload not found" }, status: :not_found
rescue => e
render json: { error: "Rename failed: #{e.message}" }, status: :unprocessable_entity
end
private
def check_quota
if params[:file].present?
file_size = params[:file].size
quota_service = QuotaService.new(current_user)
policy = quota_service.current_policy
if file_size > policy.max_file_size
usage = quota_service.current_usage
render json: quota_error_json(usage, "File size exceeds your limit of #{ActiveSupport::NumberHelper.number_to_human_size(policy.max_file_size)} per file"), status: :payment_required
return
end
unless quota_service.can_upload?(file_size)
usage = quota_service.current_usage
render json: quota_error_json(usage), status: :payment_required
nil
end
end
end
def quota_error_json(usage, custom_message = nil)
{
error: custom_message || "Storage quota exceeded",
quota: {
storage_used: usage[:storage_used],
storage_limit: usage[:storage_limit],
quota_tier: usage[:policy],
percentage_used: usage[:percentage_used]
}
}
end
def upload_json(upload)
{
id: upload.id,
filename: upload.filename.to_s,
size: upload.byte_size,
content_type: upload.content_type,
url: upload.cdn_url,
created_at: upload.created_at.iso8601
}
end
end
end
end