1 package SMMID
::Controller
::REST
::SMID
;
5 use Unicode
::Normalize
;
7 use Chemistry
::File
::SMILES
;
9 #use lib '~/SMMID/local-lib/lib/perl5/Chemistry/MolecularMass';
10 #use Chemistry::MolecularMass;
12 BEGIN { extends
'Catalyst::Controller::REST' };
17 default => 'application/json',
19 map => { 'application/json' => 'JSON' },
25 SMMID::Controller::REST::SMID - REST-based controller to manage SMIDs
35 sub rest
: Chained
('/') PathPart
('rest') CaptureArgs
(0) {
36 print STDERR
"found rest...\n";
39 sub browse
:Chained
('rest') PathPart
('browse') Args
(0) {
42 print STDERR
"found rest/browse...\n";
44 my $rs = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->search( {}, { order_by
=> { -asc
=> 'smid_id' } } );
47 while (my $r = $rs->next()) {
49 my $cur_char = "<p style=\"color:green\"><b>\x{2713}</b></p>";
50 if(!defined($r->curation_status()) || $r->curation_status() eq "unverified"){$cur_char = "<p style=\"color:red\">Unverified</p>";}
51 elsif($r->curation_status() eq "review"){$cur_char = "<p style=\"color:blue\">Marked for Review</p>";}
53 push @data, ["<a href=\"/smid/".$r->compound_id()."\">".$r->smid_id()."</a>", $r->formula(), $r->molecular_weight(), $cur_char ];
56 $c->stash->{rest
} = { data
=> \
@data };
60 sub molecular_weight
{
62 #The default variable will be used as the chemical Formula
182 my @pairs = /([CHONPS][0-9]*)/g;
183 foreach my $pair (@pairs){
184 if (length($pair)==1){
185 $weight += $elements{substr($pair, 0, 1)};
187 $weight += $elements{substr($pair, 0, 1)}*substr($pair, 1);
193 #Inserting a subroutine for the curator interface. At first, it will be a clone of the browse tab.
194 sub curator
: Chained
('rest') PathPart
('curator') Args
(0) {
197 print STDERR
"found rest/curator...\n";
199 my $rs = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->search({}, { order_by
=> { -asc
=> 'smid_id'}});
201 while (my $r = $rs->next()) {
203 my $button = "<button id=\"unverify_".$r->compound_id()."\" onclick=\"mark_smid_for_review(".$r->compound_id().")\" type=\"button\" class=\"btn btn-primary\">Mark for Review</button>";
204 my $cur_status = "<p style=\"color:green\"><b>\x{2713}</b></p>";
206 my $advice = "Approve and Curate";
209 my $hplcexperiments = $c->model("SMIDDB")->resultset("SMIDDB::Result::Experiment")->search({compound_id
=> $r->compound_id(), experiment_type
=> "hplc_ms"});
210 my $msmsexperiments = $c->model("SMIDDB")->resultset("SMIDDB::Result::Experiment")->search({compound_id
=> $r->compound_id(), experiment_type
=> "ms_spectrum"});
212 if (!$r->organisms()){push(@missing, "organisms");}
213 if (!$r->formula()){push(@missing, "Molecular Formula");}
214 if (!$r->smid_id()){push(@missing, "SMID ID");}
215 # ...requirement for HPLC-MS and MS/MS data
216 if (!$hplcexperiments->next()){push(@missing, "HPLC-MS Data");}
217 if (!$msmsexperiments->next()){push(@missing, "MS/MS Data");}
219 my $missinglist = "(Missing: ";
220 $missinglist .= join(", ", @missing);
222 if ($missinglist eq "(Missing: )"){$missinglist = "";} else {$advice = "Curation not Reccommended"; $disabled = "disabled";}
224 if(!defined($r->curation_status()) || $r->curation_status() eq "unverified"){
225 $cur_status = "<p style=\"color:red\">Unverified $missinglist </p>";
226 $button = "<button id=\"curate_".$r->compound_id()."\" onclick=\"curate_smid(".$r->compound_id().")\" type=\"button\" class=\"btn btn-primary\" $disabled>$advice</button>";
228 elsif($r->curation_status() eq "review"){
229 $cur_status = "<p style=\"color:blue\">Marked for Review $missinglist </p> ";
230 $button = "<button id=\"curate_".$r->compound_id()."\" onclick=\"curate_smid(".$r->compound_id().")\" type=\"button\" class=\"btn btn-primary\" $disabled>$advice</button>";
233 push @data, [ $r->compound_id(), "<a href=\"/smid/".$r->compound_id()."/edit\">".$r->smid_id()."</a>", $r->formula(), $r->smiles(), $button, $cur_status];
236 $c->stash->{rest
} = { data
=> \
@data };
240 #If I am correct, this subroutine formats the data, while the above subroutine collects the data
241 sub curator_format
:Chained
('rest') PathPart
('curator') Args
(1) {
248 if ($format eq "html") {
250 print STDERR
"found the curator html...\n";
252 my $html = "<table border=\"1\" width=\"100%\" cellpadding=\"10\" >\n
253 <thead><th>SMID ID</th><th>Formula</th><th>SMILES</th><th><a width=\"50\"></a>Status</th></thead>\n";
255 foreach my $smid (@
{$c->stash->{rest
}->{data
}}) {
256 $html .= "<tr><td><a href=\"/smid/$smid->[0]\">$smid->[1]</a></td><td>$smid->[2]</td><td>$smid->[3]</td><td><button id=\"curate_smid".$smid->compound_id()."\" disabled=\"false\" class=\"btn btn-primary\">Approve and Curate</button></td></tr>\n";
258 $html .= "</table>\n";
260 $c->stash->{rest
} = { html
=> $html };
263 if ($format eq "datatable") {
265 print STDERR
"found the curator data...\n";
267 my @data = $c->stash->{rest
}->{data
};
271 sub browse_format
:Chained
('rest') PathPart
('browse') Args
(1) {
278 if ($format eq "html") {
279 my $html = "<table border=\"1\" width=\"100%\" cellpadding=\"10\" >\n
280 <thead><th>SMID ID</th><th>Formula</th><th>SMILES</th></thead>\n";
282 foreach my $smid (@
{$c->stash->{rest
}->{data
}}) {
283 $html .= "<tr><td><a href=\"/smid/$smid->[0]\">$smid->[1]</a></td><td>$smid->[2]</td><td>$smid->[3]</td></tr>\n";
285 $html .= "</table>\n";
287 $c->stash->{rest
} = { html
=> $html };
290 if ($format eq "datatable") {
293 print STDERR
"found the browse data...\n";
295 my @data = $c->stash->{rest
}->{data
};
306 $str =~ s/\<script\>//gi;
307 $str =~ s/\<\/script\>//gi
;
313 sub store
:Chained
('rest') PathPart
('smid/store') Args
(0) {
318 $c->stash->{rest
} = { error
=> "Login required for updating SMIDs." };
322 my $user_id = $c->user()->get_object()->dbuser_id();
324 my $smid_id = $self->clean($c->req->param("smid_id"));
325 my $iupac_name = $self->clean($c->req->param("iupac_name"));
327 print STDERR
"IUPAC name = $iupac_name\n";
328 my $smiles_string = $self->clean($c->req->param("smiles_string"));
330 print STDERR
"SMILES = $smiles_string\n";
332 my $formula = $self->clean($c->req->param("formula"));
333 my $organisms = $self->clean($c->req->param("organisms"));
334 my $description = $self->clean($c->req->param("description"));
335 my $synonyms = $self->clean($c->req->param("synonyms"));
336 my $curation_status = $self->clean($c->req->param("curation_status"));
337 my $doi = $self->clean($c->req->param("doi"));
339 #my $mm = new Chemistry::MolecularMass;
340 my $molecular_weight = molecular_weight
($formula);
343 if (!$smid_id) { $errors .= "Need smid id. "; }
344 if (!$iupac_name) { $errors .= "Need a IUPAC name. "; }
345 if (!$smiles_string) { $errors .= "Need smiles_string. "; }
346 if (!$formula) { $errors .= "Need formula. "; }
349 $c->stash->{rest
} = { error
=> $errors };
356 smiles
=> $smiles_string,
357 organisms
=> $organisms,
359 iupac_name
=> $iupac_name,
360 curation_status
=> $curation_status,
361 dbuser_id
=> $user_id,
362 description
=> $description,
363 synonyms
=> $synonyms,
364 create_date
=> 'now()',
365 molecular_weight
=> $molecular_weight,
366 last_modified_date
=> 'now()',
371 my $new = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->new($row);
373 $compound_id = $new->compound_id();
377 $c->stash->{rest
} = { error
=> "Sorry, an error occurred storing the smid ($@)" };
381 $c->stash->{rest
} = {
382 compound_id
=> $compound_id,
383 message
=> "Successfully stored the smid $smid_id"
388 sub smid
:Chained
('rest') PathPart
('smid') CaptureArgs
(1) {
392 my $compound_id = shift;
394 $c->stash->{compound_id
} = $compound_id;
397 sub delete_smid
:Chained
('smid') PathPart
('delete') Args
(0) {
407 #This is where the backend function will go to curate a smid. Use buttons modeled on smid_detail.js for help
408 #Note that this function will both curate a smid and mark it as unverified depending on the parameters sent!
409 sub curate_smid
:Chained
('smid') PathPart
('curate_smid') Args
(0){
414 my $curation_status = $self->clean($c->req->param("curation_status"));
415 my $compound_id = $c->stash->{compound_id
};
416 my $curator_id = $c->user()->get_object()->dbuser_id();
417 my $row = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->find( { compound_id
=> $compound_id} );
420 $c->stash->{rest
} = { error
=> "The SMID with id $compound_id does not exist." };
424 my $smid_id = $row->smid_id();
429 curation_status
=> $curation_status,
431 last_modified_date
=> 'now()',
433 last_curated_time
=> 'now()',
435 curator_id
=> $curator_id
443 message
=> "Successfully updated the curation status of smid $smid_id"
446 print STDERR
"Smid ".$smid_id." curation status updated to $curation_status\n";
451 #Note that this one does not update curator id, last edited time, or last curated time. It is being marked for
452 #review, so it is inappropriate to say that the smid has been edited or curated.
453 sub mark_for_review
: Chained
('smid') PathPart
('mark_for_review') Args
(0) {
458 my $curation_status = $self->clean($c->req->param("curation_status"));
459 my $compound_id = $c->stash->{compound_id
};
460 my $row = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->find( { compound_id
=> $compound_id} );
463 $c->stash->{rest
} = { error
=> "The SMID with id $compound_id does not exist." };
467 my $smid_id = $row->smid_id();
471 curation_status
=> $curation_status,
479 message
=> "Successfully updated the curation status of smid $smid_id"
482 print STDERR
"Smid ".$smid_id." curation status updated to $curation_status NOTE TO RYAN\n";
486 sub update
:Chained
('smid') PathPart
('update') Args
(0) {
491 $c->stash->{rest
} = { error
=> "Login required for updating SMIDs." };
495 my $compound_id = $c->stash->{compound_id
};
496 my $smid_row = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->find( { compound_id
=> $compound_id } );
499 $c->stash->{rest
} = { error
=> "The SMID with id $compound_id does not exist." };
503 my $user_id = $c->user()->get_object()->dbuser_id();
504 my $smid_owner_id = $smid_row->dbuser_id();
507 if ( ($user_id != $smid_owner_id) && ($c->user->get_object()->user_type() ne "curator") ) {
508 $c->stash->{rest
} = { error
=> "The SMID with id $compound_id is (owned by $smid_owner_id) not owned by you ($user_id) and you cannot modify it." };
512 my $smid_id = $self->clean($c->req->param("smid_id"));
513 my $smiles_string = $self->clean($c->req->param("smiles_string"));
514 my $formula = $self->clean($c->req->param("formula"));
515 my $organisms = $self->clean($c->req->param("organisms"));
516 my $iupac_name = $self->clean($c->req->param("iupac_name"));
517 my $curation_status = $self->clean($c->req->param("curation_status"));
518 my $synonyms = $self->clean($c->req->param("synonyms"));
519 my $description = $self->clean($c->req->param("description"));
520 my $doi = $self->clean($c->req->param("doi"));
521 #my $mm <- new Chemistry::MolecularMass;
522 my $molecular_weight = molecular_weight
($formula);
525 if (!$compound_id) { $errors .= "Need compound id. "; }
526 if (!$iupac_name) { $errors .= "Need IUPAC name. "; }
527 if (!$smid_id) { $errors .= "Need smid id. "; }
528 #if (!$smiles_string) { $errors .= "Need smiles_string. "; }
529 if (!$formula) { $errors .= "Need formula. "; }
531 if (my $smiles_error = $self->check_smiles($smiles_string)) {
532 $errors .= $smiles_error;
536 $c->stash->{rest
} = { error
=> $errors };
543 smiles
=> $smiles_string,
544 organisms
=> $organisms,
546 iupac_name
=> $iupac_name,
547 curation_status
=> $curation_status,
548 description
=> $description,
549 synonyms
=> $synonyms,
550 molecular_weight
=> $molecular_weight,
551 last_modified_date
=> 'now()',
555 $smid_row->update($data);
559 $c->stash->{rest
} = { error
=> "Sorry, an error occurred storing the smid ($@)" };
564 compound_id
=> $compound_id,
565 message
=> "Successfully stored the smid $smid_id"
575 Chemistry
::Mol
->parse($smiles, format
=> 'smiles');
588 sub detail
:Chained
('smid') PathPart
('details') Args
(0) {
592 my $s = $c->model("SMIDDB")->resultset("SMIDDB::Result::Compound")->find( { compound_id
=> $c->stash->{compound_id
} });
595 $c->stash->{rest
} = { error
=> "Can't find smid with id ".$c->stash->{compound_id
}."\n" };
600 $data->{smid_id
} = $s->smid_id();
601 $data->{compound_id
} = $s->compound_id();
602 $data->{formula
}= $s->formula();
603 $data->{organisms
} = $s->organisms();
604 $data->{doi
} = $s->doi();
605 $data->{iupac_name
} = $s->iupac_name();
606 $data->{smiles_string
} = $s->smiles();
607 $data->{curation_status
} = $s->curation_status();
608 $data->{last_modified_date
} = $s->last_modified_date();
609 $data->{create_date
} = $s->create_date();
610 $data->{curator_id
} = $s->curator_id();
611 $data->{last_curated_time
} = $s->last_curated_time();
612 $data->{description
} = $s->description();
613 $data->{synonyms
} = $s->synonyms();
614 $data->{molecular_weight
} = $s->molecular_weight();
616 $c->stash->{rest
} = { data
=> $data };
618 print STDERR
"Found smid details...\n";
622 sub smid_dbxref
:Chained
('smid') PathPart
('dbxrefs') Args
(0) {
627 if ($c->stash->{compound_id
}) {
628 $rs = $c->model("SMIDDB")->resultset("SMIDDB::Result::Dbxref")->search( { 'compound_dbxrefs.compound_id' => $c->stash->{compound_id
} }, { join => 'compound_dbxrefs' , { join => 'db' }});
631 $c->stash->{rest
} = { data
=> [] };
637 while (my $dbxref = $rs->next()) {
638 print STDERR
"Retrieved: ". $dbxref->dbxref_id()."...\n";
641 my $display_url = "";
644 $db_name = $dbxref->db->name();
645 my $url = $dbxref->db->url();
646 my $urlprefix = $dbxref->db->urlprefix();
647 $display_url = join("", $urlprefix, $url, $dbxref->accession());
650 my $delete_link = "X";
653 $delete_link = "<a href=\"javascript:delete_dbxref(".$dbxref->dbxref_id().")\" ><font color=\"red\">X</font></a>";
655 push @
$data, [ $db_name, $dbxref->accession(), $display_url , $delete_link ];
657 $c->stash->{rest
} = { data
=> $data };
660 sub results
: Chained
('smid') PathPart
('results') Args
(0) {
664 my $experiment_type = $c->req->param("experiment_type");
666 my $rs = $c->model("SMIDDB")->resultset("SMIDDB::Result::Experiment")->search( { compound_id
=> $c->stash->{compound_id
}, experiment_type
=> $experiment_type } );
668 print STDERR
"Retrieved ".$rs->count()." rows...\n";
671 my $delete_link = "X";
672 while (my $row = $rs->next()) {
673 my $experiment_id = $row->experiment_id();
675 $delete_link = "<a href=\"javascript:delete_experiment($experiment_id)\"><font color=\"red\">X</font></a>";
678 if ($experiment_type eq "hplc_ms") {
679 my $json = $row->data();
680 my $hash = JSON
::XS
->new()->decode($json);
681 push @data, [ $hash->{hplc_ms_author
}, $hash->{hplc_ms_method_type
}, $hash->{hplc_ms_retention_time
}, $hash->{hplc_ms_ionization_mode
}, $hash->{hplc_ms_adducts_detected
}, $hash->{hplc_ms_scan_number
}, $hash->{hplc_ms_link
}, $delete_link ];
683 if ($experiment_type eq "ms_spectrum") {
684 my $json = $row->data();
685 my $hash = JSON
::XS
->new()->decode($json);
686 push @data, [ $hash->{ms_spectrum_author
}, $hash->{ms_spectrum_ionization_mode
}, $hash->{ms_spectrum_collision_energy
}, $hash->{ms_spectrum_adduct_fragmented
}, "<a href=\"/experiment/".$row->experiment_id()."\">Details</a>", $hash->{ms_spectrum_link
}, $delete_link ];
690 $c->stash->{rest
} = { data
=> \
@data };
694 sub compound_images
:Chained
('smid') PathPart
('images') Args
(1) {
699 my $rs = $c->model("SMIDDB")->resultset("SMIDDB::Result::CompoundImage")->search( { compound_id
=> $c->stash->{compound_id
} });
702 while (my $row = $rs->next()) {
703 my $image = SMMID
::Image
->new( { schema
=> $c->model("SMIDDB"), image_id
=> $row->image_id() });
705 my $delete_link = "";
707 $delete_link = "<a href=\"javascript:delete_image(".$row->image_id().", ".$c->stash->{compound_id
}.")\">X</a>";
712 if ($size =~ m/thumbnail|small|medium|large/) { $file = $size.".png"; }
713 my $image_full_url = "/".$c->config->{image_url
}."/".$image->image_subpath()."/".$file;
714 push @source_tags, "<img src=\"$image_full_url\" />$delete_link";
716 print STDERR
"returning images for compound ".$c->stash->{compound_id
} ." with size $size.\n";
717 $c->stash->{rest
} = { html
=> \
@source_tags };
726 This library is free software, you can redistribute it and/or modify
727 it under the same terms as Perl itself.