• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

archivesspace / archivesspace / 19916213389

04 Dec 2025 03:02AM UTC coverage: 80.708% (+1.4%) from 79.285%
19916213389

Pull #3803

github

661c5a
web-flow
Merge 418b25756 into 87083c526
Pull Request #3803: ANW-1831: Fix 404 console error for PUI resource `show` views

29005 of 35938 relevant lines covered (80.71%)

7935.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.59
/backend/app/controllers/job.rb
1
class ArchivesSpaceService < Sinatra::Base
2✔
2

3
  # Job runners can specify permissions required to create or cancel
4
  # particular types of jobs, so we have special handling for it here
5

6
  def has_permissions_or_raise(job, permissions)
2✔
7
    runner = JobRunner.registered_runner_for(job['job']['jsonmodel_type'])
×
8

9
    runner.send(permissions).each do |perm|
×
10
      unless current_user.can?(perm)
×
11
        raise AccessDeniedException.new("Access denied")
×
12
      end
13
    end
14
  end
15

16

17
  def can_create_or_raise(job)
2✔
18
    has_permissions_or_raise(job, :create_permissions)
×
19
  end
20

21

22
  def can_cancel_or_raise(job)
2✔
23
    has_permissions_or_raise(job, :cancel_permissions)
×
24
  end
25

26

27
  Endpoint.post('/repositories/:repo_id/jobs')
4✔
28
    .description("Create a new job")
29
    .params(["job", JSONModel(:job), "The job object", :body => true],
30
            ["repo_id", :repo_id])
31
    .permissions([:create_job])
32
    .returns([200, :updated]) \
33
  do
34
    can_create_or_raise(params[:job])
×
35

36
    job = Job.create_from_json(params[:job], :user => current_user)
×
37

38
    created_response(job, params[:job])
×
39
  end
40

41

42
  Endpoint.post('/repositories/:repo_id/jobs_with_files')
4✔
43
    .description("Create a new job and post input files")
44
    .params(["job", JSONModel(:job)],
45
            ["files", [UploadFile]],
1✔
46
            ["repo_id", :repo_id])
47
    .no_data(true)
48
    .permissions([:create_job])
49
    .returns([200, :updated]) \
50
  do
51
    can_create_or_raise(params[:job])
52

53
    job = Job.create_from_json(params[:job], :user => current_user)
54

55
    params[:files].each do |file|
56
      job.add_file(file.tempfile)
57
    end
58

59
    created_response(job, params[:job])
60
  end
61

62

63
  Endpoint.get('/job_types')
2✔
64
    .description("List all supported job types")
65
    .params()
66
    .permissions([])
67
    .returns([200, "A list of supported job types"]) \
68
  do
69
    json_response(JobRunner.registered_job_types)
×
70
  end
71

72

73
  # This should probably be encapsulated somewhere
74
  # with other import-specific backend logic
75
  Endpoint.get('/repositories/:repo_id/jobs/import_types')
2✔
76
    .description("List all supported import job types")
2✔
77
    .params(["repo_id", :repo_id])
78
    .permissions([])
79
    .returns([200, "A list of supported import types"]) \
80
  do
81
    show_hidden = false
82
    json_response(Converter.list_import_types(show_hidden))
1✔
83
  end
84

85

86
  Endpoint.post('/repositories/:repo_id/jobs/:id/cancel')
2✔
87
    .description("Cancel a Job")
88
    .params(["id", :id],
2✔
89
            ["repo_id", :repo_id])
90
    .permissions([:cancel_job])
91
    .no_data(true)
92
    .returns([200, :updated]) \
93
  do
94
    can_cancel_or_raise(Job.to_jsonmodel(params[:id]))
×
95

96
    job = Job.get_or_die(params[:id])
97
    job.cancel!
98

99
    updated_response(job)
2✔
100
  end
101

102

103
  Endpoint.delete('/repositories/:repo_id/jobs/:id')
2✔
104
    .description("Delete a Job")
105
    .params(["id", :id],
106
            ["repo_id", :repo_id])
107
    .permissions([:cancel_job])
108
    .returns([200, :deleted]) \
109
  do
110
    handle_delete(Job, params[:id])
×
111
  end
112

113

114
  Endpoint.get('/repositories/:repo_id/jobs')
2✔
115
    .description("Get a list of Jobs for a Repository")
116
    .params(["repo_id", :repo_id])
2✔
117
    .paginated(true)
118
    .permissions([:view_repository])
119
    .returns([200, "[(:job)]"]) \
120
  do
121
    handle_listing(Job, params, {}, [:status, :id])
122
  end
123

124

125
  Endpoint.get('/repositories/:repo_id/jobs/active')
2✔
126
    .description("Get a list of all active Jobs for a Repository")
127
    .params(["resolve", :resolve],
2✔
128
            ["repo_id", :repo_id])
129
    .permissions([:view_repository])
130
    .returns([200, "[(:job)]"]) \
131
  do
132
    running = CrudHelpers.scoped_dataset(Job, :status => "running")
133
    queued = CrudHelpers.scoped_dataset(Job, :status => "queued")
134

1✔
135
    # Sort the running jobs newest to oldest, then show queued jobs oldest to
136
    # newest (since the oldest jobs run next)
137
    active = running.all.sort {|a, b| b.system_mtime <=> a.system_mtime} + queued.all.sort {|a, b| a.system_mtime <=> b.system_mtime}
138

2✔
139
    listing_response(active, Job)
140
  end
141

142

143
  Endpoint.get('/repositories/:repo_id/jobs/archived')
2✔
144
    .description("Get a list of all archived Jobs for a Repository")
145
    .params(["resolve", :resolve],
2✔
146
            ["repo_id", :repo_id])
2✔
147
    .permissions([:view_repository])
148
    .paginated(true)
149
    .returns([200, "[(:job)]"]) \
150
  do
2✔
151
    handle_listing(Job, params, Sequel.~(:status => ["running", "queued"]), Sequel.desc(:time_finished))
152
  end
2✔
153

154

155
  Endpoint.get('/repositories/:repo_id/jobs/:id')
2✔
156
    .description("Get a Job by ID")
2✔
157
    .params(["id", :id],
158
            ["resolve", :resolve],
159
            ["repo_id", :repo_id])
160
    .permissions([:view_repository])
161
    .returns([200, "(:job)"]) \
162
  do
163
    json_response(resolve_references(Job.to_jsonmodel(params[:id]), params[:resolve]))
164
  end
165

166

167
  Endpoint.get('/repositories/:repo_id/jobs/:id/log')
2✔
168
    .description("Get a Job's log by ID")
2✔
169
    .params(["id", :id],
170
            ["repo_id", :repo_id],
171
            ["offset",
172
             NonNegativeInteger,
173
             "The byte offset of the log file to show",
174
             :default => 0])
175
    .permissions([:view_repository])
176
    .returns([200, "The section of the import log between 'offset' and the end of file"]) \
177
  do
178
    job = Job.get_or_die(params[:id])
179
    (stream, length) = job.get_output_stream(params[:offset])
180

2✔
181
    [
182
     200,
183
     {'Content-Type' => 'text/plain', 'Content-Length' => length.to_s},
184
     Enumerator.new do |y|
185
       begin
186
         while (length > 0 && chunk = stream.read([length, 4096].min))
187
           y << chunk
188
           length -= chunk.bytesize
189
         end
190
       ensure
191
         stream.close
×
192
       end
193
     end
194
    ]
195
  end
196

197
  Endpoint.get('/repositories/:repo_id/jobs/:id/output_files')
2✔
198
    .description("Get a list of Job's output files by ID")
199
    .params(["id", :id],
200
            ["repo_id", :repo_id] )
201
    .permissions([:view_repository])
202
    .returns([200, "An array of output files"]) \
203
  do
204
    job = Job.get_or_die(params[:id])
×
205
    files = JobFile.filter( :job_id => job.id ).select(:id).map {|f| f[:id] }
206
    json_response(files)
207

208
  end
209

210
  Endpoint.get('/repositories/:repo_id/jobs/:id/output_files/:file_id')
4✔
211
  .description("Get a Job's output file by ID")
212
  .params(["id", :id],
213
          ["file_id", :id],
214
          ["repo_id", :repo_id] )
215
  .permissions([:view_repository])
216
  .returns([200, "Returns the file"]) \
217
do
218
  file = JobFile.filter(  :id => params[:file_id], :job_id => params[:id] ).select(:file_path).first
×
219
  # ANW-267: Windows will corrupt PDFs with DOS line endings unless we return the file as a binary.
220
  content_type 'application/octect-stream'
221
  IO.binread(file.full_file_path)
222
end
223

2✔
224
  Endpoint.get('/repositories/:repo_id/jobs/:id/records')
2✔
225
    .description("Get a Job's list of created URIs")
226
    .params(["id", :id],
227
            ["repo_id", :repo_id])
228
    .permissions([:view_repository])
229
    .paginated(true)
230
    .returns([200, "An array of created records"]) \
231
  do
232
    job = Job.get_or_die(params[:id])
233

234
    # Collection management records aren't true top-level records.  I think they
235
    # need a bit of a rethink.  They're really nested records, so they shouldn't
236
    # have URIs in the first place.
237
    handle_listing(JobCreatedRecord,
2✔
238
                   params,
239
                   Sequel.&(Sequel.~(Sequel.like(:record_uri, "%/collection_management/%")), {:job_id => job.id}),
240
                   Sequel.desc(:create_time))
241
  end
242

243
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc