diff --git a/CHANGELOG.md b/CHANGELOG.md
index 11d7fe67..65d08759 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
# Changelog
+## [1.0.1-rc.10](https://github.com/NFDI4Chem/nmrxiv/compare/v1.0.0-rc.10...v1.0.1-rc.10) (2023-11-16)
+
+
+### Bug Fixes
+
+* added molecular search ([8dfcc4a](https://github.com/NFDI4Chem/nmrxiv/commit/8dfcc4a0a2b218ee68e7d39d8916ac44e93af508))
+* enable listing of files with missing flag at root and title udpates ([05adf74](https://github.com/NFDI4Chem/nmrxiv/commit/05adf74dbf0161c38475821f7c5528597d717a4a))
+* molecules filtering and other bug fixes ([bb3d701](https://github.com/NFDI4Chem/nmrxiv/commit/bb3d7012123df556c63188443b9741e7a8ea2ab9))
+* updated molecular card link and studies filtering ([cf6763e](https://github.com/NFDI4Chem/nmrxiv/commit/cf6763e6f595285682cc870f5dd5c44bf6d0a3ef))
+
## [1.0.1-rc.9](https://github.com/NFDI4Chem/nmrxiv/compare/v1.0.0-rc.9...v1.0.1-rc.9) (2023-11-15)
diff --git a/app/Console/Commands/SanitizeMolecules.php b/app/Console/Commands/SanitizeMolecules.php
index bbacd0d2..28e19b7f 100644
--- a/app/Console/Commands/SanitizeMolecules.php
+++ b/app/Console/Commands/SanitizeMolecules.php
@@ -27,17 +27,17 @@ class SanitizeMolecules extends Command
*/
public function handle()
{
- $molecules = Molecule::whereNull('CANONICAL_SMILES')->get();
+ $molecules = Molecule::whereNull('canonical_smiles')->get();
foreach ($molecules as $molecule) {
echo $molecule->id;
echo "\r\n";
$molecule->MOL = '
'.$molecule->MOL;
- $standardisedMOL = $this->standardizeMolecule($molecule->MOL);
- $molecule->CANONICAL_SMILES = $standardisedMOL['canonical_smiles'];
- $molecule->STANDARD_INCHI = $standardisedMOL['inchi'];
- $molecule->INCHI_KEY = $standardisedMOL['inchikey'];
+ $standardisedMOL = $this->standardizeMolecule($molecule->sdf);
+ $molecule->canonical_smiles = $standardisedMOL['canonical_smiles'];
+ $molecule->standard_inchi = $standardisedMOL['inchi'];
+ $molecule->inchi_key = $standardisedMOL['inchikey'];
$molecule->save();
}
}
diff --git a/app/Http/Controllers/API/Schemas/Bioschema/BiochemaController.php b/app/Http/Controllers/API/Schemas/Bioschema/BiochemaController.php
index 05d1fa34..405c2013 100644
--- a/app/Http/Controllers/API/Schemas/Bioschema/BiochemaController.php
+++ b/app/Http/Controllers/API/Schemas/Bioschema/BiochemaController.php
@@ -230,7 +230,7 @@ public function getMolecules($sample)
{
$molecules = [];
foreach ($sample->molecules as &$molecule) {
- $inchiKey = $molecule->INCHI_KEY;
+ $inchiKey = $molecule->inchi_key;
$pubchemLink = 'https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/inchikey/'.$inchiKey.'/property/IUPACName/JSON';
$json = json_decode(Http::get($pubchemLink)->body(), true);
// $cid = $json['PropertyTable']['Properties'][0]['CID'];
@@ -240,14 +240,14 @@ public function getMolecules($sample)
// $moleculeSchema['@id'] = $inchiKey;
// $moleculeSchema['dct:conformsTo'] = $this->conformsTo(['https://bioschemas.org/profiles/MolecularEntity/0.5-RELEASE']);
// $moleculeSchema['identifier'] = $inchiKey;
- // $moleculeSchema->name($molecule->CAS_NUMBER);
+ // $moleculeSchema->name($molecule->cas);
// $moleculeSchema->url('https://pubchem.ncbi.nlm.nih.gov/compound/'.$cid);
- // $moleculeSchema->inChI('InChI='.$molecule->STANDARD_INCHI);
+ // $moleculeSchema->inChI('InChI='.$molecule->standard_inchi);
// $moleculeSchema->inChIKey($inchiKey);
// $moleculeSchema->iupacName($iupacName);
- // $moleculeSchema->molecularFormula($molecule->FORMULA);
- // $moleculeSchema->molecularWeight($molecule->MOLECULAR_WEIGHT);
- // $moleculeSchema->smiles([$molecule->SMILES, $molecule->SMILES_CHIRAL, $molecule->CANONICAL_SMILES]);
+ // $moleculeSchema->molecularFormula($molecule->molecular_formula);
+ // $moleculeSchema->molecularWeight($molecule->molecular_weight);
+ // $moleculeSchema->smiles([$molecule->SMILES, $molecule->absolute_smiles, $molecule->canonical_smiles]);
// $moleculeSchema->hasRepresentation($molecule->MOL);
// $moleculeSchema->description('Percentage composition: '.$molecule->pivot->percentage_composition.'%');
// array_push($molecules, $moleculeSchema);
diff --git a/app/Http/Controllers/API/SearchController.php b/app/Http/Controllers/API/SearchController.php
new file mode 100644
index 00000000..f4dd4ff8
--- /dev/null
+++ b/app/Http/Controllers/API/SearchController.php
@@ -0,0 +1,336 @@
+query('limit');
+ $sort = $request->query('sort');
+ $limit = $limit ? $limit : 24;
+ $page = $request->query('page');
+ $tagType = $request->get('tagType') ? $request->get('tagType') : null;
+
+ $offset =
+ (($page != null && $page != 'null' && $page != 0 ? $page : 1) -
+ 1) *
+ $limit;
+
+ $query = $request->get('query');
+
+ $type = $request->query('type')
+ ? $request->query('type')
+ : $request->get('type');
+
+ if ($type) {
+ $queryType = $type;
+ } else {
+ //inchi
+ $re =
+ '/^((InChI=)?[^J][0-9BCOHNSOPrIFla+\-\(\)\\\\\/,pqbtmsih]{6,})$/i';
+ preg_match_all($re, $query, $imatches, PREG_SET_ORDER, 0);
+
+ if (count($imatches) > 0 && substr($query, 0, 6) == 'InChI=') {
+ $queryType = 'inchi';
+ }
+
+ //inchikey
+ $re = '/^([0-9A-Z\-]+)$/i';
+ preg_match_all($re, $query, $ikmatches, PREG_SET_ORDER, 0);
+ if (
+ count($ikmatches) > 0 &&
+ substr($query, 14, 1) == '-' &&
+ strlen($query) == 27
+ ) {
+ $queryType = 'inchikey';
+ }
+
+ // smiles
+ $re = '/^([^J][0-9BCOHNSOPrIFla@+\-\[\]\(\)\\\\\/%=#$]{6,})$/i';
+ preg_match_all($re, $query, $matches, PREG_SET_ORDER, 0);
+
+ if (count($matches) > 0 && substr($query, 14, 1) != '-') {
+ $queryType = 'smiles';
+ }
+ }
+
+ $filterMap = [
+ 'mf' => 'molecular_formula',
+
+ 'mw' => 'molecular_weight',
+ 'hac' => 'heavy_atom_count',
+ 'tac' => 'total_atom_count',
+
+ 'arc' => 'aromatic_ring_count',
+ 'rbc' => 'rotatable_bond_count',
+ 'mrc' => 'minimal_number_of_rings',
+ 'fc' => 'formal_charge',
+ 'cs' => 'contains_sugar',
+ 'crs' => 'contains_ring_sugars',
+ 'cls' => 'contains_linear_sugars',
+
+ 'npl' => 'np_likeness_score',
+ 'alogp' => 'alogp',
+ 'topopsa' => 'topo_psa',
+ 'fsp3' => 'fsp3',
+ 'hba' => 'h_bond_acceptor_count',
+ 'hbd' => 'h_bond_donor_count',
+ 'ro5v' => 'rule_of_5_violations',
+ 'lhba' => 'lipinski_h_bond_acceptor_count',
+ 'lhbd' => 'lipinski_h_bond_donor_count',
+ 'lro5v' => 'lipinski_rule_of_5_violations',
+ 'ds' => 'found_in_databases',
+
+ 'class' => 'chemical_class',
+ 'subclass' => 'chemical_sub_class',
+ 'superclass' => 'chemical_super_class',
+ 'parent' => 'direct_parent_classification',
+
+ ];
+
+ $queryType = strtolower($queryType);
+
+ $statement = null;
+
+ if ($queryType == 'smiles' || $queryType == 'substructure') {
+ $statement =
+ "select id, COUNT(*) OVER () from mols where m@>'".
+ $query.
+ "' limit ".
+ $limit.
+ ' offset '.
+ $offset;
+ } elseif ($queryType == 'inchi') {
+ $statement =
+ "select id, COUNT(*) OVER () from molecules WHERE identifier NOTNULL AND standard_inchi LIKE '%".
+ $query.
+ "%' limit ".
+ $limit.
+ ' offset '.
+ $offset;
+ } elseif ($queryType == 'inchikey') {
+ $statement =
+ "select id, COUNT(*) OVER () from molecules WHERE identifier NOTNULL AND standard_inchi_key LIKE '%".
+ $query.
+ "%' limit ".
+ $limit.
+ ' offset '.
+ $offset;
+ } elseif ($queryType == 'exact') {
+ $statement =
+ "select id, COUNT(*) OVER () from mols where m@='".
+ $query.
+ "' limit ".
+ $limit.
+ ' offset '.
+ $offset;
+ } elseif ($queryType == 'similarity') {
+ $statement =
+ "select id, COUNT(*) OVER () from fps where mfp2%morganbv_fp('".
+ $query.
+ "') limit ".
+ $limit.
+ ' offset '.
+ $offset;
+ } elseif ($queryType == 'tags') {
+ $results = Molecule::withAnyTags([$query], $tagType)->paginate($limit)->items();
+ $count = Molecule::withAnyTags([$query], $tagType)->count();
+ } elseif ($queryType == 'filters') {
+ $orConditions = explode('OR', $query);
+ $isORInitial = true;
+ $statement =
+ 'select molecule_id as id, COUNT(*) OVER () from properties where ';
+ foreach ($orConditions as $orCondition) {
+ if ($isORInitial === false) {
+ $statement = $statement.' OR ';
+ }
+ $isORInitial = false;
+ $statement = $statement.'(';
+ $andConditions = explode(' ', trim($orCondition, ' '));
+ $isANDInitial = true;
+ foreach ($andConditions as $andCondition) {
+ if ($isANDInitial === false) {
+ $statement = $statement.' AND ';
+ }
+ $isANDInitial = false;
+ $_filter = explode(':', $andCondition);
+ if (str_contains($_filter[1], '..')) {
+ $range = array_values(explode('..', $_filter[1]));
+ $statement =
+ $statement.
+ '('.
+ $filterMap[$_filter[0]].
+ ' between '.
+ $range[0].
+ ' and '.
+ $range[1].
+ ')';
+ } elseif (
+ $_filter[1] === 'true' ||
+ $_filter[1] === 'false'
+ ) {
+ $statement =
+ $statement.
+ '('.
+ $filterMap[$_filter[0]].
+ ' = '.
+ $_filter[1].
+ ')';
+ } elseif (str_contains($_filter[1], '|')) {
+ $dbFilters = explode('|', $_filter[1]);
+ $dbs = explode('+', $dbFilters[0]);
+ $statement =
+ $statement.
+ '('.
+ $filterMap[$_filter[0]].
+ " @> '[\"".
+ implode('","', $dbs).
+ "\"]')";
+ } else {
+ if (str_contains($_filter[1], '+')) {
+ $_filter[1] = str_replace('+', ' ', $_filter[1]);
+ }
+ $statement =
+ $statement.
+ '('.$filterMap[$_filter[0]].'::TEXT ILIKE \'%'.$_filter[1].'%\')';
+ }
+ }
+ $statement = $statement.')';
+ }
+ $statement = $statement.' LIMIT '.$limit;
+ // dd($statement );
+ } else {
+ if ($query) {
+ $query = str_replace("'", "''", $query);
+ $statement =
+ "select id, COUNT(*) OVER () from molecules WHERE identifier NOTNULL AND (\"name\"::TEXT ILIKE '%".
+ $query.
+ "%') OR (\"synonyms\"::TEXT ILIKE '%".
+ $query.
+ "%') OR (\"identifier\"::TEXT ILIKE '%".
+ $query.
+ "%') limit ".
+ $limit.
+ ' offset '.
+ $offset;
+ } else {
+ $statement =
+ 'select id, COUNT(*) OVER () from mols limit '.
+ $limit.
+ ' offset '.
+ $offset;
+ }
+ }
+ if ($statement) {
+ $expression = DB::raw($statement);
+ $qString = $expression->getValue(
+ DB::connection()->getQueryGrammar()
+ );
+
+ $hits = DB::select($qString);
+
+ $count = count($hits) > 0 ? $hits[0]->count : 0;
+
+ $ids = implode(
+ ',',
+ collect($hits)
+ ->pluck('id')
+ ->toArray()
+ );
+
+ if ($ids != '') {
+ $statement =
+ 'SELECT * FROM molecules WHERE identifier NOTNULL AND ID IN ('.
+ implode(
+ ',',
+ collect($hits)
+ ->pluck('id')
+ ->toArray()
+ ).
+ ')';
+ if ($sort == 'recent') {
+ $statement = $statement.' ORDER BY created_at DESC';
+ }
+ $expression = DB::raw($statement);
+ $string = $expression->getValue(
+ DB::connection()->getQueryGrammar()
+ );
+ $results = DB::select($string);
+ } else {
+ $results = [];
+ $count = 0;
+ }
+ }
+ $pagination = new LengthAwarePaginator(
+ $results,
+ $count,
+ $limit,
+ $page
+ );
+
+ //dd($pagination);
+ return $pagination;
+ } catch (QueryException $exception) {
+ return response()->json(
+ [
+ 'message' => $exception->getMessage(),
+ ],
+ 500
+ );
+ }
+ }
+}
diff --git a/app/Http/Controllers/ApplicationController.php b/app/Http/Controllers/ApplicationController.php
index 18c994a6..53f76077 100644
--- a/app/Http/Controllers/ApplicationController.php
+++ b/app/Http/Controllers/ApplicationController.php
@@ -11,6 +11,22 @@
class ApplicationController extends Controller
{
+ /**
+ * Resolve the incoming compounds search request and renders compounds
+ * inertia view
+ *
+ * @return Inertia\Inertia
+ */
+ public function compounds(Request $request)
+ {
+ $query = $request->get('query');
+ $limit = $request->get('limit') ? $limit : 24;
+ $page = $request->query('page');
+ $tagType = $request->query('tagType') ? $request->query('tagType') : null;
+
+ return Inertia::render('Public/Compounds', compact(['query', 'limit', 'page', 'tagType']));
+ }
+
/**
* Resolve the incoming request into right models and render the
* inertia view
diff --git a/app/Http/Controllers/DraftController.php b/app/Http/Controllers/DraftController.php
index eacde9f6..0b744b04 100644
--- a/app/Http/Controllers/DraftController.php
+++ b/app/Http/Controllers/DraftController.php
@@ -87,7 +87,6 @@ public function files(Request $request, Draft $draft)
'children' => FileSystemObject::with('children')
->where([
['level', 0],
- ['status', '<>', 'missing'],
['draft_id', $draft->id],
])
->orderBy('created_at', 'DESC')
diff --git a/app/Http/Controllers/StudyController.php b/app/Http/Controllers/StudyController.php
index 21c06f4c..5613d0cb 100644
--- a/app/Http/Controllers/StudyController.php
+++ b/app/Http/Controllers/StudyController.php
@@ -29,13 +29,23 @@ class StudyController extends Controller
{
public function publicStudiesView(Request $request)
{
- // $datasets = Cache::rememberForever('datasets', function () {
- $studies = StudyResource::collection(Study::with('datasets')->where([['is_public', true], ['is_archived', false]])->filter($request->only('search', 'sort', 'mode'))->paginate(12)->withQueryString());
- // });
+ $moleculeId = $request->get('compound');
+ if ($moleculeId) {
+ $molecule = Molecule::where('identifier', $moleculeId)->first();
+ if ($molecule) {
+ $studies = $molecule->studies()->where([['is_public', true], ['is_archived', false]])->filter($request->only('search', 'sort', 'mode'))->paginate(12)->withQueryString();
+ } else {
+ $studies = [];
+ }
+ } else {
+ $studies = Study::with('datasets')->where([['is_public', true], ['is_archived', false]])->filter($request->only('search', 'sort', 'mode'))->paginate(12)->withQueryString();
+ }
+
+ $studiesResource = StudyResource::collection($studies);
return Inertia::render('Public/Studies', [
'filters' => $request->all('search', 'sort', 'mode'),
- 'studies' => $studies,
+ 'studies' => $studiesResource,
]);
}
@@ -133,15 +143,15 @@ public function moleculeStore(Request $request, Study $study)
$study->sample()->save($sample);
}
$inchi = $request->get('InChI');
- $molecule = $sample->molecules->where('STANDARD_INCHI', $inchi)->first();
+ $molecule = $sample->molecules->where('standard_inchi', $inchi)->first();
if (is_null($molecule)) {
$molecule = Molecule::firstOrCreate([
- 'STANDARD_INCHI' => $inchi,
+ 'standard_inchi' => $inchi,
], [
- 'FORMULA' => $request->get('formula') ? $request->get('formula') : '',
- 'INCHI_KEY' => $request->get('InChIKey') ? $request->get('InChIKey') : '',
- 'MOL' => $request->get('mol') ? $request->get('mol') : '',
- 'CANONICAL_SMILES' => $request->get('canonical_smiles') ? $request->get('canonical_smiles') : '',
+ 'molecular_formula' => $request->get('formula') ? $request->get('formula') : '',
+ 'inchi_key' => $request->get('InChIKey') ? $request->get('InChIKey') : '',
+ 'sdf' => $request->get('mol') ? $request->get('mol') : '',
+ 'canonical_smiles' => $request->get('canonical_smiles') ? $request->get('canonical_smiles') : '',
]);
$sample->molecules()->syncWithPivotValues([$molecule->id], ['percentage_composition' => $request->get('percentage')], false);
}
diff --git a/app/Jobs/ArchiveStudy.php b/app/Jobs/ArchiveStudy.php
index 5ba6748b..82b909f6 100644
--- a/app/Jobs/ArchiveStudy.php
+++ b/app/Jobs/ArchiveStudy.php
@@ -158,15 +158,15 @@ public function handle(): void
// $standardizedMolecule = $this->standardizeMolecule($mol['molfile']);
// // associate
// $inchi = $standardizedMolecule['InChI'];
- // $molecule = $sample->molecules->where('STANDARD_INCHI', $inchi)->first();
+ // $molecule = $sample->molecules->where('standard_inchi', $inchi)->first();
// if (is_null($molecule)) {
// $molecule = Molecule::firstOrCreate([
- // 'STANDARD_INCHI' => $inchi,
+ // 'standard_inchi' => $inchi,
// ], [
- // 'FORMULA' => $standardizedMolecule['formula'] ? $standardizedMolecule['formula'] : '',
- // 'INCHI_KEY' => $standardizedMolecule['InChIKey'] ? $standardizedMolecule['InChIKey'] : '',
- // 'MOL' => $standardizedMolecule['mol'] ? $standardizedMolecule['mol'] : '',
- // 'CANONICAL_SMILES' => $standardizedMolecule['canonical_smiles'] ? $standardizedMolecule['canonical_smiles'] : '',
+ // 'molecular_formula' => $standardizedMolecule['formula'] ? $standardizedMolecule['formula'] : '',
+ // 'inchi_key' => $standardizedMolecule['InChIKey'] ? $standardizedMolecule['InChIKey'] : '',
+ // 'sdf' => $standardizedMolecule['mol'] ? $standardizedMolecule['mol'] : '',
+ // 'canonical_smiles' => $standardizedMolecule['canonical_smiles'] ? $standardizedMolecule['canonical_smiles'] : '',
// ]);
// $sample->molecules()->syncWithPivotValues([$molecule->id], ['percentage_composition' => 0], false);
// }
diff --git a/app/Models/Molecule.php b/app/Models/Molecule.php
index 99385695..1aeec025 100644
--- a/app/Models/Molecule.php
+++ b/app/Models/Molecule.php
@@ -11,44 +11,19 @@ class Molecule extends Model
use HasFactory;
protected $fillable = [
- 'CAS_NUMBER',
- 'FORMULA',
- 'MOLECULAR_WEIGHT',
- 'SMILES',
- 'SMILES_CHIRAL',
- 'CANONICAL_SMILES',
- 'INCHI',
- 'STANDARD_INCHI',
- 'INCHI_KEY',
- 'STANDARD_INCHI_KEY',
- 'fp0',
- 'fp1',
- 'fp2',
- 'fp3',
- 'fp4',
- 'fp5',
- 'fp6',
- 'fp7',
- 'fp8',
- 'fp9',
- 'fp10',
- 'fp11',
- 'fp12',
- 'fp13',
- 'fp14',
- 'fp15',
- 'DBE',
- 'SSSR',
- 'SAR',
+ 'cas',
+ 'molecular_formula',
+ 'molecular_weight',
+ 'smiles',
+ 'absolute_smiles',
+ 'canonical_smiles',
+ 'inchi',
+ 'standard_inchi',
+ 'inchi_key',
+ 'standard_inchi_key',
'COMMENT',
- 'fORMULA',
- 'MULTIPLICITY_0',
- 'MULTIPLICITY_1',
- 'MULTIPLICITY_2',
- 'MULTIPLICITY_3',
- 'VIEWS',
- 'DOI',
- 'MOL',
+ 'doi',
+ 'sdf',
];
/**
@@ -67,4 +42,15 @@ public function samples()
->withPivot('percentage_composition')
->withTimestamps();
}
+
+ public function studies()
+ {
+ $samples = $this->samples->load('study');
+ $studies = [];
+ foreach ($samples as $sample) {
+ array_push($studies, $sample->study->id);
+ }
+
+ return Study::whereIn('id', $studies);
+ }
}
diff --git a/app/Models/Sample.php b/app/Models/Sample.php
index b5a80ce2..38c1796c 100644
--- a/app/Models/Sample.php
+++ b/app/Models/Sample.php
@@ -32,6 +32,6 @@ public function molecules()
*/
public function study()
{
- return $this->belongsTo(Sample::class);
+ return $this->belongsTo(Study::class, 'study_id');
}
}
diff --git a/database/factories/MoleculeFactory.php b/database/factories/MoleculeFactory.php
index 368dc357..152aca2d 100644
--- a/database/factories/MoleculeFactory.php
+++ b/database/factories/MoleculeFactory.php
@@ -30,9 +30,9 @@ public function definition()
$output = [];
$labels = [
- 'InChI' => 'STANDARD_INCHI',
- 'InChIKey' => 'INCHI_KEY',
- 'Molecular Formula' => 'FORMULA',
+ 'InChI' => 'standard_inchi',
+ 'InChIKey' => 'inchi_key',
+ 'Molecular Formula' => 'molecular_formula',
];
foreach ($data as $key => $value) {
@@ -48,16 +48,16 @@ public function definition()
return
[
- 'CAS_NUMBER' => null,
- 'FORMULA' => $output['FORMULA'],
- 'MOLECULAR_WEIGHT' => null,
- 'SMILES' => null,
- 'SMILES_CHIRAL' => null,
- 'CANONICAL_SMILES' => null,
- 'INCHI' => null,
- 'STANDARD_INCHI' => $output['STANDARD_INCHI'],
- 'INCHI_KEY' => $output['INCHI_KEY'],
- 'STANDARD_INCHI_KEY' => null,
+ 'cas' => null,
+ 'molecular_formula' => $output['molecular_formula'],
+ 'molecular_weight' => null,
+ 'smiles' => null,
+ 'absolute_smiles' => null,
+ 'canonical_smiles' => null,
+ 'inchi' => null,
+ 'standard_inchi' => $output['standard_inchi'],
+ 'inchi_key' => $output['inchi_key'],
+ 'standard_inchi_key' => null,
'fp0' => null,
'fp1' => null,
'fp2' => null,
@@ -78,7 +78,7 @@ public function definition()
'SSSR' => null,
'SAR' => null,
'COMMENT' => null,
- 'MOL' => null, //todo: add mol file
+ 'sdf' => null,
'MULTIPLICITY_0' => null,
'MULTIPLICITY_1' => null,
'MULTIPLICITY_2' => null,
diff --git a/database/migrations/2023_11_15_214811_update_molecules_table.php b/database/migrations/2023_11_15_214811_update_molecules_table.php
new file mode 100644
index 00000000..fca91acb
--- /dev/null
+++ b/database/migrations/2023_11_15_214811_update_molecules_table.php
@@ -0,0 +1,150 @@
+renameColumn('INCHI', 'inchi');
+ // $table->renameColumn('STANDARD_INCHI', 'standard_inchi');
+ // $table->renameColumn('INCHI_KEY', 'inchi_key');
+ // $table->renameColumn('STANDARD_INCHI_KEY', 'standard_inchi_key');
+
+ // $table->renameColumn('canonical_smiles', 'canonical_smiles');
+ // $table->renameColumn('smiles', 'smiles');
+ // $table->renameColumn('SMILES_CHIRAL', 'absolute_smiles');
+
+ // $table->renameColumn('CAS_NUMBER', 'cas');
+ // $table->renameColumn('MOLECULAR_WEIGHT', 'molecular_weight');
+ // $table->renameColumn('FORMULA', 'molecular_formula');
+ // $table->renameColumn('MOL', 'sdf');
+
+ $table->longText('name')->nullable();
+ $table->integer('name_trust_level')->default(0)->nullable();
+ $table->integer('annotation_level')->default(0)->nullable();
+
+ $table->longText('synonyms')->nullable();
+ $table->longText('iupac_name')->nullable();
+
+ $table->json('2d')->nullable();
+ $table->json('3d')->nullable();
+
+ $table->longText('structural_comments')->nullable();
+
+ $table->enum('status', ['APPROVED', 'REVOKED'])->default('APPROVED');
+
+ $table->boolean('active')->default(1);
+ $table->boolean('has_stereo')->default(0);
+
+ $table->boolean('has_variants')->default(0);
+ $table->integer('variants_count')->default(0);
+
+ $table->dropColumn('VIEWS');
+ $table->dropColumn('COMMENT');
+ $table->dropColumn('DBE');
+ $table->dropColumn('SSSR');
+ $table->dropColumn('SAR');
+
+ $table->dropColumn('MULTIPLICITY_0');
+ $table->dropColumn('MULTIPLICITY_1');
+ $table->dropColumn('MULTIPLICITY_2');
+ $table->dropColumn('MULTIPLICITY_3');
+
+ $table->dropColumn('fp0');
+ $table->dropColumn('fp1');
+ $table->dropColumn('fp2');
+ $table->dropColumn('fp3');
+ $table->dropColumn('fp4');
+ $table->dropColumn('fp5');
+ $table->dropColumn('fp6');
+ $table->dropColumn('fp7');
+ $table->dropColumn('fp8');
+ $table->dropColumn('fp9');
+ $table->dropColumn('fp10');
+ $table->dropColumn('fp11');
+ $table->dropColumn('fp12');
+ $table->dropColumn('fp13');
+ $table->dropColumn('fp14');
+ $table->dropColumn('fp15');
+
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('molecules', function (Blueprint $table) {
+ $table->bigInteger('fp0')->nullable();
+ $table->bigInteger('fp1')->nullable();
+ $table->bigInteger('fp2')->nullable();
+ $table->bigInteger('fp3')->nullable();
+ $table->bigInteger('fp4')->nullable();
+ $table->bigInteger('fp5')->nullable();
+ $table->bigInteger('fp6')->nullable();
+ $table->bigInteger('fp7')->nullable();
+ $table->bigInteger('fp8')->nullable();
+ $table->bigInteger('fp9')->nullable();
+ $table->bigInteger('fp10')->nullable();
+ $table->bigInteger('fp11')->nullable();
+ $table->bigInteger('fp12')->nullable();
+ $table->bigInteger('fp13')->nullable();
+ $table->bigInteger('fp14')->nullable();
+ $table->bigInteger('fp15')->nullable();
+ $table->float('DBE')->nullable();
+ $table->integer('SSSR')->nullable();
+ $table->integer('SAR')->nullable();
+ $table->json('COMMENT')->nullable();
+ $table->longText('MOL')->nullable();
+ $table->integer('MULTIPLICITY_0')->nullable();
+ $table->integer('MULTIPLICITY_1')->nullable();
+ $table->integer('MULTIPLICITY_2')->nullable();
+ $table->integer('MULTIPLICITY_3')->nullable();
+ $table->integer('VIEWS')->nullable();
+
+ // $table->renameColumn('inchi', 'INCHI');
+ // $table->renameColumn('standard_inchi','STANDARD_INCHI');
+ // $table->renameColumn('inchi_key', 'INCHI_KEY');
+ // $table->renameColumn('standard_inchi_key', 'STANDARD_INCHI_KEY');
+
+ // $table->renameColumn('canonical_smiles','canonical_smiles');
+ // $table->renameColumn('smiles', 'SMILES');
+ // $table->renameColumn('absolute_smiles', 'SMILES_CHIRAL');
+
+ // $table->renameColumn('cas', 'CAS_NUMBER');
+ // $table->renameColumn('molecular_weight', 'MOLECULAR_WEIGHT');
+ // $table->renameColumn('molecular_formula', 'FORMULA');
+
+ // $table->renameColumn('comment', 'COMMENT');
+ // $table->renameColumn('sdf', 'MOL');
+
+ $table->dropColumn('name');
+ $table->dropColumn('name_trust_level');
+ $table->dropColumn('annotation_level');
+
+ $table->dropColumn('synonyms');
+ $table->dropColumn('iupac_name');
+
+ $table->dropColumn('2d');
+ $table->dropColumn('3d');
+
+ $table->dropColumn('structural_comments');
+
+ $table->dropColumn('status');
+
+ $table->dropColumn('active');
+ $table->dropColumn('has_stereo');
+
+ $table->dropColumn('has_variants');
+ $table->dropColumn('variants_count');
+ });
+ }
+};
diff --git a/database/scripts/initialise_molecules_index.sql b/database/scripts/initialise_molecules_index.sql
new file mode 100644
index 00000000..3bcacf26
--- /dev/null
+++ b/database/scripts/initialise_molecules_index.sql
@@ -0,0 +1,19 @@
+create extension if not exists rdkit;
+select * into mols from (select id,mol_from_smiles(canonical_smiles::cstring) m from molecules) tmp where m is not null;
+create index molidx on mols using gist(m);
+alter table mols add primary key (id);
+
+-- select * from molecules where identifier = 'CNP0228556' limit 24 offset 0;
+-- SELECT * from properties where molecule_id = 139702;
+-- select id, COUNT(*) OVER () from molecules where name = 'Curcumin' limit 24;
+-- select count(*) from molecules where smiles@>'c1cccc2c1nncc2' ;
+-- select id, COUNT(*) OVER () from molecules where LOWER(synonyms) LIKE '%' || LOWER('Thein') || '%' limit 24 offset 0;
+-- select id, COUNT(*) OVER () from molecules where LOWER(name) LIKE LOWER('Caffeine') OR LOWER(synonyms) LIKE '%' || LOWER('Caffeine') || '%' ORDER BY CASE WHEN (name) LIKE LOWER('Caffeine') THEN 1 WHEN LOWER(synonyms) LIKE '%' || LOWER('Caffeine') || '%' THEN 2 END;
+
+select id, torsionbv_fp(m) as torsionbv,morganbv_fp(m) as mfp2, featmorganbv_fp(m) as ffp2 into fps from mols;
+create index fps_ttbv_idx on fps using gist(torsionbv);
+create index fps_mfp2_idx on fps using gist(mfp2);
+create index fps_ffp2_idx on fps using gist(ffp2);
+alter table fps add primary key (id);
+
+-- select identifier, standard_inchi, standard_inchi_key, properties.score from molecules RIGHT JOIN properties ON properties.molecule_id = molecules.id;
\ No newline at end of file
diff --git a/resources/js/App/MoleculeCard.vue b/resources/js/App/MoleculeCard.vue
new file mode 100644
index 00000000..24c4443b
--- /dev/null
+++ b/resources/js/App/MoleculeCard.vue
@@ -0,0 +1,157 @@
+
+
+
+ Search spectra by compound names,
+ structure, sub-structures and
+ smiliarity.
+
+ Showing
+ {{
+ results.from
+ }}
+ to
+ {{
+ results.to
+ }}
+ of
+ {{
+ results.total
+ }}
+ results
+
+ {{ error.status }}
+
+ {{ error.data.message }}
+
+ Try to refine your search query.
+ If the problem persist please
+ contact us.
+
+ Showing
+ {{
+ results.from
+ }}
+ to
+ {{
+ results.to
+ }}
+ of
+ {{
+ results.total
+ }}
+ results
+
+ Filters
+
+
+
+
+ {{ error.statusText }}
+
+
+ No records were found
+
+
+ Recent Searches
+
+
+
+