diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml new file mode 100644 index 00000000..47e35743 --- /dev/null +++ b/.github/workflows/docs-deploy.yml @@ -0,0 +1,64 @@ +# Sample workflow for building and deploying a VitePress site to GitHub Pages +# +name: Deploy Coconut docs to Pages + +on: + # Runs on pushes targeting the `main` branch. Change this to `master` if you're + # using the `master` branch as the default branch. + push: + branches: [main] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: pages + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Not needed if lastUpdated is not enabled + # - uses: pnpm/action-setup@v3 # Uncomment this if you're using pnpm + # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm # or pnpm / yarn + - name: Setup Pages + uses: actions/configure-pages@v4 + - name: Install dependencies + run: npm ci # or pnpm install / yarn install / bun install + - name: Build with VitePress + run: npm run docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + name: Deploy + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/app/Actions/Coconut/SearchMolecule.php b/app/Actions/Coconut/SearchMolecule.php index 428801ca..bef124cb 100644 --- a/app/Actions/Coconut/SearchMolecule.php +++ b/app/Actions/Coconut/SearchMolecule.php @@ -2,6 +2,7 @@ namespace App\Actions\Coconut; +use App\Models\Citation; use App\Models\Collection; use App\Models\Molecule; use App\Models\Organism; @@ -26,6 +27,8 @@ class SearchMolecule public $organisms = null; + public $citations = null; + /** * Search based on given query. */ @@ -140,6 +143,8 @@ private function getFilterMap() 'subclass' => 'chemical_sub_class', 'superclass' => 'chemical_super_class', 'parent' => 'direct_parent_classification', + 'org' => 'organism', + 'cite' => 'ciatation', ]; } @@ -233,6 +238,18 @@ private function buildTagsStatement($offset) return Molecule::whereHas('organisms', function ($query) use ($organismIds) { $query->whereIn('organism_id', $organismIds); })->where('active', true)->where('is_parent', false)->orderBy('annotation_level', 'DESC')->paginate($this->size); + } elseif ($this->tagType == 'citations') { + $this->citations = array_map('strtolower', array_map('trim', explode(',', $this->query))); + $citationIds = Citation::where(function ($query) { + foreach ($this->citations as $name) { + $query->orWhereRaw('LOWER(doi) LIKE ?', ['%'.strtolower($name).'%']) + ->orWhereRaw('LOWER(title) LIKE ?', ['%'.strtolower($name).'%']); + } + })->pluck('id'); + + return Molecule::whereHas('citations', function ($query) use ($citationIds) { + $query->whereIn('citation_id', $citationIds); + })->where('active', true)->where('is_parent', false)->orderBy('annotation_level', 'DESC')->paginate($this->size); } else { return Molecule::withAnyTags([$this->query], $this->tagType)->where('active', true)->where('is_parent', false)->paginate($this->size); } diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index 6884919b..31b4735f 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -30,7 +30,7 @@ public function create(array $input): User ])->validate(); return User::create([ - 'name' => $input['username'], + 'name' => $input['first_name'].' '.$input['last_name'], 'email' => $input['email'], 'password' => Hash::make($input['password']), diff --git a/app/Console/Commands/DashWidgetsRefresh.php b/app/Console/Commands/DashWidgetsRefresh.php index 23fd33f3..e4b5a62a 100644 --- a/app/Console/Commands/DashWidgetsRefresh.php +++ b/app/Console/Commands/DashWidgetsRefresh.php @@ -32,7 +32,7 @@ class DashWidgetsRefresh extends Command public function handle() { // Clear the cache for all widgets - Cache::flush(); + // Cache::flush(); // Create the cache for all DashboardStats widgets Cache::rememberForever('stats.collections', function () { diff --git a/app/Http/Controllers/API/Auth/RegisterController.php b/app/Http/Controllers/API/Auth/RegisterController.php index 0e2aa5da..e2136c66 100644 --- a/app/Http/Controllers/API/Auth/RegisterController.php +++ b/app/Http/Controllers/API/Auth/RegisterController.php @@ -25,7 +25,7 @@ public function register(Request $request): JsonResponse 'last_name' => $validatedData['last_name'], 'email' => $validatedData['email'], 'username' => $validatedData['username'], - 'name' => $validatedData['username'], + 'name' => $validatedData['first_name'].' '.$validatedData['last_name'], 'orcid_id' => $request['orcid_id'], 'affiliation' => $request['affiliation'], 'password' => Hash::make($validatedData['password']), diff --git a/app/Livewire/Search.php b/app/Livewire/Search.php index acce2ba9..998ea89d 100644 --- a/app/Livewire/Search.php +++ b/app/Livewire/Search.php @@ -37,6 +37,9 @@ class Search extends Component public $organisms = null; + #[Url(as: 'activeTab')] + public $activeTab = 'molecules'; + public function placeholder() { return <<<'HTML' @@ -65,16 +68,6 @@ public function placeholder() HTML; } - protected $listeners = ['updateSmiles' => 'setSmiles']; - - public function setSmiles($smiles, $searchType) - { - $this->query = $smiles; - $this->type = $searchType; - $this->page = null; - $this->tagType = null; - } - public function updatedQuery() { $this->page = 1; @@ -82,6 +75,11 @@ public function updatedQuery() $this->tagType = null; } + public function search(SearchMolecule $search) + { + $this->render($search); + } + public function render(SearchMolecule $search) { try { diff --git a/app/Livewire/StructureEditor.php b/app/Livewire/StructureEditor.php index e46e6c30..65c1fb40 100644 --- a/app/Livewire/StructureEditor.php +++ b/app/Livewire/StructureEditor.php @@ -10,6 +10,8 @@ class StructureEditor extends Component public $smiles; + public $type = 'substructure'; + public function mount($smiles) { $this->smiles = $smiles; diff --git a/app/Livewire/Welcome.php b/app/Livewire/Welcome.php index c1c06178..6c9c850a 100644 --- a/app/Livewire/Welcome.php +++ b/app/Livewire/Welcome.php @@ -18,13 +18,6 @@ class Welcome extends Component public $citationsMapped; - protected $listeners = ['updateSmiles' => 'setSmiles']; - - public function setSmiles($smiles, $searchType) - { - return redirect()->to('/search?q='.urlencode($smiles).'&type='.urlencode($searchType)); - } - public function placeholder() { return <<<'HTML' diff --git a/app/Models/User.php b/app/Models/User.php index 7307f709..06d1b247 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -87,4 +87,16 @@ public function linkedSocialAccounts() { return $this->hasMany(LinkedSocialAccount::class); } + + /** + * Check if user can access a particular panel. + */ + public function canAccessPanel(Panel $panel): bool + { + if ($panel->getId() === 'control-panel') { + return $this->roles()->exists(); + } + + return true; + } } diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 8743d0c6..cabfc71e 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -13,13 +13,8 @@ public function up(): void { Schema::create('users', function (Blueprint $table) { $table->id(); - $table->string('name')->nullable(); + $table->string('name'); $table->string('email')->unique(); - $table->string('username')->unique(); - $table->string('first_name'); - $table->string('last_name'); - $table->string('orcid_id')->nullable(); - $table->string('affiliation')->nullable(); $table->timestamp('email_verified_at')->nullable(); $table->string('password')->nullable(); $table->rememberToken(); diff --git a/database/migrations/2024_08_08_150749_add_columns_to_users_table.php b/database/migrations/2024_08_08_150749_add_columns_to_users_table.php new file mode 100644 index 00000000..d7200885 --- /dev/null +++ b/database/migrations/2024_08_08_150749_add_columns_to_users_table.php @@ -0,0 +1,33 @@ +string('name')->nullable()->change(); + $table->string('username')->unique()->nullable(); + $table->string('first_name')->nullable(); + $table->string('last_name')->nullable(); + $table->string('orcid_id')->nullable(); + $table->string('affiliation')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn(['username', 'first_name', 'last_name', 'orcid_id', 'affiliation']); + }); + } +}; diff --git a/docs/.vitepress/cache/deps/_metadata.json b/docs/.vitepress/cache/deps/_metadata.json index ee217db1..a4f4d32e 100644 --- a/docs/.vitepress/cache/deps/_metadata.json +++ b/docs/.vitepress/cache/deps/_metadata.json @@ -1,31 +1,31 @@ { - "hash": "f6991786", + "hash": "911d3a82", "configHash": "a6dc731f", - "lockfileHash": "ba8dc5dd", - "browserHash": "6d39fbea", + "lockfileHash": "65d0b609", + "browserHash": "3f416605", "optimized": { "vue": { "src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js", "file": "vue.js", - "fileHash": "81b3a260", + "fileHash": "698daf6a", "needsInterop": false }, "vitepress > @vue/devtools-api": { "src": "../../../../node_modules/@vue/devtools-api/dist/index.js", "file": "vitepress___@vue_devtools-api.js", - "fileHash": "085c132f", + "fileHash": "abeac802", "needsInterop": false }, "vitepress > @vueuse/core": { "src": "../../../../node_modules/@vueuse/core/index.mjs", "file": "vitepress___@vueuse_core.js", - "fileHash": "d9cc147b", + "fileHash": "354fcef1", "needsInterop": false }, "@theme/index": { "src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js", "file": "@theme_index.js", - "fileHash": "5ba4d4f8", + "fileHash": "d4ea2476", "needsInterop": false } }, diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.mts similarity index 54% rename from docs/.vitepress/config.ts rename to docs/.vitepress/config.mts index fb75a3bd..2de021c2 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.mts @@ -3,7 +3,8 @@ import { defineConfig } from 'vitepress' // https://vitepress.dev/reference/site-config export default defineConfig({ title: "COCONUT Docs", - description: "COCONUT: the COlleCtion of Open Natural prodUcTs", + description: "COCONUT: the COlleCtion of Open NatUral producTs", + base: '/coconut/', themeConfig: { // https://vitepress.dev/reference/default-theme-config logo: '/logo.png', @@ -23,11 +24,11 @@ export default defineConfig({ ], nav: [ - { text: 'Home', link: '/' }, - { text: 'Guides', link: '/single-submission' }, - { text: 'API', link: '/auth-api' }, - { text: 'About', link: '/about' }, - { text: 'Contact', link: '/contact' } + { text: 'Home', link: '/introduction' }, + { text: 'Guides', link: '/collection-submission' }, + { text: 'API', link: 'https://coconut.cheminf.studio/api-documentation' }, + { text: 'About', link: 'https://coconut.cheminf.studio/about' }, + { text: 'Download', link: 'https://coconut.cheminf.studio/download' } ], sidebar: [ @@ -36,35 +37,45 @@ export default defineConfig({ items: [ { text: 'Introduction', link: '/introduction' }, { text: 'Sources', link: '/sources' }, - { text: 'Analysis', link: '/analysis' }, + // { text: 'Analysis', link: '/analysis' }, ] }, { - text: 'Search', + text: 'Browse/Search', items: [ - { text: 'Simple', link: '/simple-search' }, + { text: 'Browse', link: '/browse' }, + // { text: 'Simple', link: '/simple-search' }, { text: 'Structure', link: '/structure-search' }, + // { + // text: 'Structure', + // items: [ + // { text: 'Draw Structure', link: '/draw-structure' }, + // { text: 'Substructure Search', link: '/substructure-search' }, + // { text: 'Similarity Search', link: '/similarity-search' }, + // ] + // }, { text: 'Advanced', link: '/advanced-search' } ] }, { text: 'Submission Guides', items: [ - { text: 'Single Compound Submission', link: '/single-submission' }, - { text: 'Multiple Compound Submission', link: '/multi-submission' }, - { text: 'Programmatic Submission via API', link: '/api-submission' } - ] - }, - { - text: 'API', - items: [ - { text: 'Auth', link: '/auth-api' }, - { text: 'Search', link: '/search-api' }, - { text: 'Schemas', link: '/schemas-api' }, - { text: 'Download', link: '/download-api' }, - { text: 'Submission', link: '/submission-api' } + { text: 'Collection Submission', link: '/collection-submission' }, + // { text: 'Single Compound Submission', link: '/single-submission' }, + // { text: 'Multiple Compound Submission', link: '/multi-submission' }, + { text: 'Reporting', link: '/report-submission' } ] }, + // { + // text: 'API', + // items: [ + // { text: 'Auth', link: '/auth-api' }, + // { text: 'Search', link: '/search-api' }, + // { text: 'Schemas', link: '/schemas-api' }, + // { text: 'Download', link: '/download-api' }, + // { text: 'Submission', link: '/submission-api' } + // ] + // }, { text: 'Download', link:'/download', items: [ @@ -83,7 +94,7 @@ export default defineConfig({ ], socialLinks: [ - { icon: 'github', link: 'https://github.com/vuejs/vitepress' } + { icon: 'github', link: 'https://github.com/Steinbeck-Lab/coconut' } ] } }) diff --git a/docs/.vitepress/dist/404.html b/docs/.vitepress/dist/404.html index 678f76ae..d4a7228f 100644 --- a/docs/.vitepress/dist/404.html +++ b/docs/.vitepress/dist/404.html @@ -6,16 +6,16 @@