-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathapp.rb
More file actions
506 lines (429 loc) · 13.1 KB
/
app.rb
File metadata and controls
506 lines (429 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# :)
# add current directory and lib directory to load path
$LOAD_PATH << __dir__
$LOAD_PATH << File.join(__dir__, "lib")
require 'open-uri'
require 'timeout'
require 'net/http'
require 'net/ping'
require 'uri'
require 'json'
require 'fileutils'
require 'bundler'
Bundler.require
require 'lib/pj'
ENV["DATABASE_URL"] ||= "sqlite3:database.sqlite"
class PeakJohn < Sinatra::Base
register Sinatra::ActiveRecordExtension
set :database, ENV["DATABASE_URL"]
helpers do
def app_root
"#{env["rack.url_scheme"]}://#{env["HTTP_HOST"]}#{env["SCRIPT_NAME"]}"
end
def wabi_endpoint_status
Timeout.timeout(3) do
URI.open(settings.wabi_endpoint).read
end
rescue OpenURI::HTTPError
nil
rescue Timeout::Error
nil
end
end
private
def self.download_json_with_fallback(remote_url, local_filename)
local_path = File.join("public", local_filename)
if File.exist?(local_path)
puts "Using cached file: #{local_path}"
return JSON.load(File.read(local_path))
end
begin
puts "No cached file found, downloading from remote: #{remote_url}"
Timeout.timeout(30) do
content = URI.open(remote_url).read
File.write(local_path, content)
JSON.load(content)
end
rescue => e
puts "Failed to download #{remote_url}: #{e.message}"
raise "Unable to load #{remote_url} and no cached file available"
end
end
configure do
# Static config (no external dependencies)
set :wabi_endpoint, "https://dtn1.ddbj.nig.ac.jp/wabi/chipatlas/"
unless ENV["SKIP_APP_CONFIGURE"]
# Database-dependent settings (app cannot function without these)
set :number_of_experiments, ((PJ::Experiment.number_of_experiments / 1000) * 1000).to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
set :index_all_genome, PJ::Experiment.index_all_genome
set :list_of_genome, PJ::Experiment.list_of_genome
set :list_of_experiment_types, PJ::Experiment.list_of_experiment_types
set :qval_range, PJ::Bedfile.qval_range
set :colo_analysis, PJ::Analysis.results(:colo)
set :target_genes_analysis, PJ::Analysis.results(:target_genes)
set :bedsizes, PJ::Bedsize.dump
# Remote JSON data and derived settings (app cannot function without these)
set :experiment_list, download_json_with_fallback("https://chip-atlas.dbcls.jp/data/metadata/ExperimentList.json", "ExperimentList.json")
set :experiment_list_adv, download_json_with_fallback("https://chip-atlas.dbcls.jp/data/metadata/ExperimentList_adv.json", "ExperimentList_adv.json")
PJ::ExperimentSearch.load_from_json(settings.experiment_list_adv)
set :gsm_to_srx, Hash[settings.experiment_list["data"].map{|a| [a[2], a[0]] }]
end
end
configure :production do
set :host_authorization, { permitted_hosts: [".chip-atlas.org"] }
end
before do
rack_input = request.env["rack.input"].read
if !rack_input.empty?
posted_data = JSON.parse(rack_input) rescue nil
if posted_data
log = [Time.now, request.ip, request.path_info, posted_data].join("\t")
logfile = "./log/access_log"
logdir = File.dirname(logfile)
FileUtils.mkdir(logdir) if !File.exist?(logdir)
open(logfile,"a"){|f| f.puts(log) }
end
end
end
after do
ActiveRecord::Base.connection_handler.clear_active_connections!
end
get "/:source.css" do
sass params[:source].intern
end
get "/data/:data.json" do
data = case params[:data]
when "index_all_genome"
settings.index_all_genome
when "list_of_genome"
settings.list_of_genome.keys
when "list_of_experiment_types"
settings.list_of_experiment_types
when "qval_range"
settings.qval_range
when "exp_metadata"
PJ::Experiment.record_by_expid(params[:expid])
when "colo_analysis"
# settings.colo_analysis
PJ::Analysis.colo_result_by_genome(params[:genome])
when "target_genes_analysis"
settings.target_genes_analysis
when "number_of_lines"
settings.bedsizes
when "index_subclass"
genome = params[:genome]
ag_class = params[:agClass]
cl_class = params[:clClass]
subclass_type = params[:type]
PJ::Experiment.get_subclass(genome, ag_class, cl_class, subclass_type)
when "ExperimentList"
settings.experiment_list
when "ExperimentList_adv"
settings.experiment_list_adv
end
content_type "application/json"
JSON.dump(data)
end
get '/data/experiment_types' do
genome = params[:genome]
cl_class = params[:clClass]
data = PJ::Experiment.experiment_types(genome, cl_class)
content_type "application/json"
JSON(data)
end
get '/data/sample_types' do
genome = params[:genome]
ag_class = params[:agClass]
data = PJ::Experiment.sample_types(genome, ag_class)
content_type "application/json"
JSON(data)
end
get '/data/chip_antigen' do
genome = params[:genome]
ag_class = params[:agClass]
cl_class = params[:clClass]
data = PJ::Experiment.chip_antigen(genome, ag_class, cl_class)
content_type "application/json"
JSON(data)
end
get '/data/cell_type' do
genome = params[:genome]
ag_class = params[:agClass]
cl_class = params[:clClass]
data = PJ::Experiment.cell_type(genome, ag_class, cl_class)
content_type "application/json"
JSON(data)
end
get '/data/search' do
query = params[:q]
genome = params[:genome]
limit = (params[:limit] || 20).to_i.clamp(1, 100)
data = PJ::ExperimentSearch.search(query, genome: genome, limit: limit)
content_type "application/json"
JSON(data)
end
get "/health" do
checks = {}
# DB connectivity
begin
ActiveRecord::Base.connection.execute("SELECT 1")
checks[:database] = "ok"
rescue => e
checks[:database] = "error"
checks[:database_error] = e.message
end
# Config loaded (settings populated during configure block)
config_loaded = begin
settings.respond_to?(:list_of_genome) && settings.list_of_genome
rescue
false
end
checks[:config] = config_loaded ? "ok" : "not_loaded"
healthy = checks[:database] == "ok" &&
(ENV["RACK_ENV"] == "production" ? checks[:config] == "ok" : true)
status healthy ? 200 : 503
content_type "application/json"
JSON.dump({ status: healthy ? "ok" : "error", checks: checks })
end
get "/" do
@number_of_experiments = settings.number_of_experiments
haml :about
end
get "/qvalue_range" do
content_type "application/json"
JSON(settings.qval_range)
end
#
# Peak Browser
#
get "/peak_browser" do
@index_all_genome = settings.index_all_genome
@list_of_genome = settings.list_of_genome
@qval_range = settings.qval_range
haml :peak_browser
end
post "/browse" do
request.body.rewind
json = request.body.read
content_type "application/json"
url = PJ::Location.new(JSON.parse(json)).igv_browsing_url
JSON.dump({ "url" => url })
end
post "/download" do
request.body.rewind
json = request.body.read
content_type "application/json"
url = PJ::Location.new(JSON.parse(json)).archive_url
puts "DOWNLOAD: JSON: #{JSON.parse(json)}, URL: #{url}"
JSON.dump({ "url" => url })
end
get "/view" do
@expid = params[:id].upcase
if @expid =~ /^GSM/
redirect "/view?id=#{settings.gsm_to_srx[@expid]}"
end
redirect "not_found", 404 if !PJ::Experiment.id_valid?(@expid)
@ncbi = PJ::SRA.new(@expid).fetch
haml :experiment
end
#
# Colocalization analysis
#
get "/colo" do
@index_all_genome = settings.index_all_genome
@list_of_genome = settings.list_of_genome
haml :colo
end
post "/colo" do
request.body.rewind
json = request.body.read
content_type "application/json"
colo_url = PJ::Location.new(JSON.parse(json)).colo_url(params[:type])
JSON.dump({ "url" => colo_url })
end
get "/colo_result" do
@iframe_url = params[:base]
# haml :colo_result
if remotefile_available?(@iframe_url)
redirect @iframe_url
else
redirect "not_found", 404
end
end
#
# Target genes analysis
#
get "/target_genes" do
@index_all_genome = settings.index_all_genome
@list_of_genome = settings.list_of_genome
haml :target_genes
end
post "/target_genes" do
request.body.rewind
json = request.body.read
content_type "application/json"
target_genes_url = PJ::Location.new(JSON.parse(json)).target_genes_url(params[:type])
JSON.dump({ "url" => target_genes_url })
end
get "/target_genes_result" do
@iframe_url = params[:base]
# haml :target_genes_result
if remotefile_available?(@iframe_url)
redirect @iframe_url
else
redirect "not_found", 404
end
end
#
# Enrichment analysis
#
get "/enrichment_analysis" do
@index_all_genome = settings.index_all_genome
@list_of_genome = settings.list_of_genome
@qval_range = settings.qval_range
haml :enrichment_analysis
end
post "/enrichment_analysis" do
request.body.rewind
params_raw = request.body.read
params_arr = params_raw.split("&").map{|k_v| k_v.split("=") }
params = Hash[params_arr]
@taxonomy = params["taxonomy"]
@genes = params["genes"]
@genesetA = params["genesetA"]
@genesetB = params["genesetB"]
@index_all_genome = settings.index_all_genome
@list_of_genome = settings.list_of_genome
@qval_range = settings.qval_range
haml :enrichment_analysis
end
get "/enrichment_analysis_result" do
haml :enrichment_analysis_result
end
#
# Diff Analysis
#
get "/diff_analysis" do
@index_all_genome = settings.index_all_genome
@list_of_genome = settings.list_of_genome
@qval_range = settings.qval_range
haml :diff_analysis
end
get "/diff_analysis_result" do
haml :diff_analysis_result
end
get "/diff_analysis_log" do
begin
URI.open("https://dtn1.ddbj.nig.ac.jp/wabi/chipatlas/#{params[:id]}?info=result&format=log").read
rescue => e
status 404
"Log file not available yet"
end
end
get "/enrichment_analysis_log" do
begin
URI.open("https://dtn1.ddbj.nig.ac.jp/wabi/chipatlas/#{params[:id]}?info=result&format=log").read
rescue => e
status 404
"Log file not available yet"
end
end
post "/diff_analysis_estimated_time" do
# Memo: from Zou-san
# X = Total # of reads
# Y = Time (sec)
# DMR: Y = 117.13 * ln(X) - 2012.5
# Diffbind: Y = 1.80 * 10^-6 X + 119.38
# add 10 mins for file conversion
request.body.rewind
data = JSON.parse(request.body.read)
a_type = data["analysis"]
total_number_of_reads = PJ::Experiment.total_number_of_reads(data["ids"]).to_i
seconds = case a_type
when 'dmr'
117.13 * Math.log(total_number_of_reads) - 2012.5 + 600
when 'diffbind'
1.80e-6 * total_number_of_reads + 119.38 + 600
else
nil
end
if seconds and !seconds.infinite?
JSON.dump({ minutes: Rational(seconds, 60).to_f.round() })
else
JSON.dump({ minutes: nil })
end
end
#
# Experiment search
#
get "/search" do
haml :search
end
#
# Publication page
#
get "/publications" do
haml :publications
end
get "/agents" do
haml :agents
end
get "/demo" do
haml :demo
end
#
# 404 Not Found
#
not_found do
haml :not_found
end
#
# DDBJ Supercomputer system WABI API
#
get "/wabi_endpoint_status" do
wabi_endpoint_status || ""
end
# Checking the final html output rather than using Wabi API which is too slow due to its huge job history
get "/wabi_chipatlas" do
server_url = "https://dtn1.ddbj.nig.ac.jp"
endpoint = "/wabi/chipatlas/#{params[:id]}?info=result&format=html"
if Net::Ping::HTTP.new(server_url).ping
response = Net::HTTP.get_response(URI.parse(server_url + endpoint))
if response.code == "200"
"finished"
else
"running"
end
else
"server unavailable"
end
end
# Post a job to DDBJ-SC via wabi API
post "/wabi_chipatlas" do
if wabi_endpoint_status != 'chipatlas'
status 503
else
# Handle both JSON and form data
post_data = if request.content_type&.include?('application/json')
request.body.rewind
JSON.parse(request.body.read)
else
params
end
wabi_response = Net::HTTP.post_form(URI.parse(settings.wabi_endpoint), post_data)
wabi_response_body = wabi_response.body
if wabi_response_body
id = wabi_response_body.split("\n").select{|n| n =~ /^requestId/ }.first.split("\s").last
JSON.dump({ "requestId" => id })
else
JSON.dump({ "request_body" => post_data.to_s })
end
end
rescue => e
puts "ERROR: #{e}, BODY: #{wabi_response_body}"
redirect "not_found", 404
end
get "/api/remoteUrlStatus" do
Net::HTTP.get_response(URI.parse(params[:url])).code.to_i
end
end