Skip to content

Commit 427efb7

Browse files
authored
Merge pull request #244 from psu-libraries/242-243-api-pages
Count pages on API upload
2 parents 1e057db + 13aa2be commit 427efb7

File tree

16 files changed

+246
-27
lines changed

16 files changed

+246
-27
lines changed

.circleci/config.yml

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -249,20 +249,9 @@ jobs:
249249
command: |
250250
gem install bundler -v "$(grep -A 1 'BUNDLED WITH' Gemfile.lock | tail -n 1 | xargs)"
251251
bundle _$(grep -A 1 'BUNDLED WITH' Gemfile.lock | tail -n 1 | xargs)_ install
252-
- attach_workspace:
253-
at: /tmp
254252
- run:
255-
name: Load Docker image from build-image
256-
command: |
257-
if [ -f /tmp/docker-image.tar ]; then
258-
docker load -i /tmp/docker-image.tar
259-
else
260-
echo "No persisted image found; falling back to local build"
261-
docker-compose -f docker-compose.yml build
262-
fi
263-
- run:
264-
name: Start services (no-build when image available)
265-
command: docker-compose -f docker-compose.yml up --force-recreate -d --no-build
253+
name: Build and run containers
254+
command: docker-compose -f docker-compose.yml build && docker-compose -f docker-compose.yml up --force-recreate -d
266255
- run:
267256
name: Wait for mysql to be ready
268257
command: docker-compose exec web bash ./wait_for_db.sh
@@ -336,6 +325,3 @@ workflows:
336325
name: test-application
337326
context:
338327
- org-global
339-
requires:
340-
- build-image
341-

Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ gem 'image_processing'
4545
# Alt-text generation
4646
gem 'alt_text'
4747

48+
# PDF page counting
49+
gem 'pdf-reader'
50+
4851
group :development, :test do
4952
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
5053
gem 'debug', platforms: %i[mri mswin mswin64 mingw x64_mingw], require: 'debug/prelude'

Gemfile.lock

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
GEM
22
remote: https://rubygems.org/
33
specs:
4+
Ascii85 (2.0.1)
45
actioncable (7.2.2.2)
56
actionpack (= 7.2.2.2)
67
activesupport (= 7.2.2.2)
@@ -78,6 +79,7 @@ GEM
7879
tzinfo (~> 2.0, >= 2.0.5)
7980
addressable (2.8.7)
8081
public_suffix (>= 2.0.2, < 7.0)
82+
afm (1.0.0)
8183
alt_text (0.1.0)
8284
aws-sdk-bedrockruntime (~> 1.55.0)
8385
dotenv (~> 3.1.8)
@@ -172,6 +174,7 @@ GEM
172174
ffi (1.17.2-x86_64-linux-gnu)
173175
globalid (1.2.1)
174176
activesupport (>= 6.1)
177+
hashery (2.1.2)
175178
i18n (1.14.7)
176179
concurrent-ruby (~> 1.0)
177180
image_processing (1.14.0)
@@ -259,6 +262,12 @@ GEM
259262
parser (3.3.9.0)
260263
ast (~> 2.4.1)
261264
racc
265+
pdf-reader (2.15.1)
266+
Ascii85 (>= 1.0, < 3.0, != 2.0.0)
267+
afm (>= 0.2.1, < 2)
268+
hashery (~> 2.0)
269+
ruby-rc4
270+
ttfunk
262271
pp (0.6.2)
263272
prettyprint
264273
prettyprint (0.2.0)
@@ -404,6 +413,7 @@ GEM
404413
rubocop (~> 1.72, >= 1.72.1)
405414
rubocop-rspec (~> 3.5)
406415
ruby-progressbar (1.13.0)
416+
ruby-rc4 (0.1.5)
407417
ruby-vips (2.2.5)
408418
ffi (~> 1.12)
409419
logger
@@ -460,6 +470,8 @@ GEM
460470
thor (1.4.0)
461471
tilt (2.7.0)
462472
timeout (0.4.3)
473+
ttfunk (1.8.0)
474+
bigdecimal (~> 3.1)
463475
turbo-rails (2.0.23)
464476
actionpack (>= 7.1.0)
465477
railties (>= 7.1.0)
@@ -509,6 +521,7 @@ DEPENDENCIES
509521
jbuilder
510522
mysql2 (~> 0.5.7)
511523
niftany
524+
pdf-reader
512525
puma (>= 5.0)
513526
rails (~> 7.2.2, >= 7.2.2.1)
514527
rails_admin

app/jobs/api_remediation_job.rb

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,27 @@ class APIRemediationJob < ApplicationJob
55

66
def perform(job_uuid, output_polling_timeout: OUTPUT_POLLING_TIMEOUT)
77
job = PdfJob.find_by!(uuid: job_uuid)
8+
tempfile = nil
89
tempfile = Down.download(job.source_url)
910
original_filename = tempfile&.original_filename
1011
job.update!(output_object_key: original_filename)
12+
13+
page_count = PDF::Reader.new(tempfile.path).page_count
14+
PageCountQuotaValidator.validate!(owner: job.owner, page_count: page_count)
15+
job.update!(page_count: page_count)
16+
1117
safe_original_filename = original_filename.gsub(/[^A-Za-z0-9.\-_ ]/, '')
1218
object_key = "#{SecureRandom.hex(8)}_#{safe_original_filename}"
1319
file_path = tempfile&.path
1420
s3_handler = S3Handler.new(object_key)
1521
s3_handler.upload_to_input(file_path)
1622
poll_and_update(job_uuid, s3_handler, output_polling_timeout)
17-
rescue S3Handler::Error => e
18-
update_with_failure(job, "Failed to upload file to remediation input location: #{e.message}")
19-
rescue Down::Error => e
23+
rescue Down::Error,
24+
S3Handler::Error,
25+
PDF::Reader::MalformedPDFError,
26+
PageCountQuotaValidator::Error => e
2027
# We may want to retry the download depending on the more specific nature of the failure.
21-
update_with_failure(job, "Failed to download file from source URL: #{e.message}")
28+
update_with_failure(job, "Failed to process job: #{e.class}: #{e.message}")
2229
ensure
2330
RemediationStatusNotificationJob.perform_later(job_uuid)
2431
tempfile&.close!

app/models/api_user.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# frozen_string_literal: true
22

33
class APIUser < ApplicationRecord
4+
include OwnerHelpers
5+
46
belongs_to :unit, optional: true
57

68
has_many :jobs, as: :owner, dependent: :restrict_with_exception
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# frozen_string_literal: true
2+
3+
module OwnerHelpers
4+
def total_pages_processed_last_24_hours
5+
jobs
6+
.where(created_at: 24.hours.ago..)
7+
.where.not(page_count: nil)
8+
.sum(:page_count)
9+
end
10+
end

app/models/unit.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ class Unit < ApplicationRecord
88
validates :daily_page_limit, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
99
validates :overall_page_limit, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
1010

11+
def total_pages_processed
12+
api_users.joins(:jobs).sum(:page_count) + gui_users.joins(:jobs).sum(:page_count)
13+
end
14+
1115
RailsAdmin.config do |config|
1216
config.model 'Unit' do
1317
list do
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# frozen_string_literal: true
2+
3+
class PageCountQuotaValidator
4+
class Error < StandardError; end
5+
6+
class QuotaExceededError < Error; end
7+
class MissingUnitError < Error; end
8+
class InvalidPageCountError < Error; end
9+
10+
def self.validate!(owner:, page_count:)
11+
unit = owner&.unit
12+
raise MissingUnitError, 'Owner must belong to a unit to validate page quota' if unit.nil?
13+
14+
page_count_int = Integer(page_count)
15+
raise InvalidPageCountError, 'page_count must be greater than 0' if page_count_int <= 0
16+
17+
total_quota = unit.overall_page_limit
18+
total_processed = unit.total_pages_processed
19+
20+
if page_count_int + total_processed > total_quota
21+
raise QuotaExceededError, "page_count exceeds the unit's overall page limit of #{total_quota}"
22+
end
23+
24+
todays_quota = unit.daily_page_limit
25+
todays_processed = owner.total_pages_processed_last_24_hours
26+
27+
if page_count_int + todays_processed > todays_quota
28+
raise QuotaExceededError, "page_count exceeds the user's daily page limit of #{todays_quota}"
29+
end
30+
31+
true
32+
rescue ArgumentError, TypeError
33+
raise InvalidPageCountError, 'page_count must be an integer'
34+
end
35+
end

db/migrate/20260203200259_add_page_count_to_job.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# frozen_string_literal: true
2+
13
class AddPageCountToJob < ActiveRecord::Migration[7.2]
24
def change
35
add_column :jobs, :page_count, :integer, null: true

db/schema.rb

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)