From f5a44b73983e05080cf10fde5869a8d41cd7b01f Mon Sep 17 00:00:00 2001 From: Cooper Bailey Date: Thu, 22 Feb 2024 19:29:00 -0800 Subject: [PATCH 01/67] added format for tutorials page and started working on tutorial endpoints --- backend/src/api/tutorials.py | 49 +++++++++++++++-- frontend/src/lib/openapi/models/Link.ts | 9 +++ frontend/src/lib/openapi/models/Tutorial.ts | 6 +- .../lib/openapi/services/DefaultService.ts | 2 +- frontend/src/routes/Tutorials.svelte | 55 ++++++++++++++++--- 5 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 frontend/src/lib/openapi/models/Link.ts diff --git a/backend/src/api/tutorials.py b/backend/src/api/tutorials.py index 446a22ad..6f78ebd9 100644 --- a/backend/src/api/tutorials.py +++ b/backend/src/api/tutorials.py @@ -1,18 +1,57 @@ from fastapi import APIRouter from ..api_types import CamelModel +# from ..db import Database router = APIRouter() -class Tutorial(CamelModel): +class Link(CamelModel): + url: str title: str +class Tutorial(CamelModel): + header: str | None = None + title: str | None = None + summary: str | None = None + links: list[Link] | None = None + +class MultipleTutorials(CamelModel): + tutorials: list[Tutorial] | None = None + + + + @router.get("/tutorials", response_model=list[Tutorial]) def get_all_tutorials(): return [ - Tutorial(title="Tutorial 1"), - Tutorial(title="Tutorial 2"), - Tutorial(title="Tutorial 3"), - Tutorial(title="Tutorial 4"), + Tutorial(title="Tutorial Page", summary="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce gravida tristique est, a sollicitudin nulla varius ac. Duis sed lacus arcu. Mauris eget condimentum justo. Vestibulum iaculis cursus accumsan. Mauris eget diam consequat, viverra dui malesuada, maximus nisl. Etiam laoreet venenatis odio ut tempus. Praesent risus est, eleifend id purus non, varius rutrum nisi. Fusce sagittis lorem nec tristique efficitur. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam pretium, sapien et volutpat tempus, elit urna congue massa, id consequat leo dolor in ligula. Sed vestibulum tristique eros eu aliquet."), + Tutorial(title="Tutorial 2", summary="Future links to second tutorial page", links=[ + Link(url="url1", title="Link 1"), + Link(url="url2", title="Link 2"), + ] + ), + Tutorial(title="Tutorial 3", summary="Future links to third tutorial page", links=[ + Link(url="url3", title="Link 3"), + Link(url="url4", title="Link 4"), + ] + ), + Tutorial(title="Tutorial 4", summary="Future links to forth tutorial page", links=[ + Link(url="url5", title="Link 5"), + Link(url="url6", title="Link 6"), + ] + ) ] + + + # try: + # with Database() as db: + # query = """SELECT name as species_name FROM species""" + # entry_sql = db.execute_return(query) + # if entry_sql is not None: + # for d in entry_sql: + + # return [d[0] for d in entry_sql] + # except Exception: + # return + diff --git a/frontend/src/lib/openapi/models/Link.ts b/frontend/src/lib/openapi/models/Link.ts new file mode 100644 index 00000000..97415b0c --- /dev/null +++ b/frontend/src/lib/openapi/models/Link.ts @@ -0,0 +1,9 @@ +/* generated using openapi-typescript-codegen -- do no edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type Link = { + url: string; + title: string; +}; + diff --git a/frontend/src/lib/openapi/models/Tutorial.ts b/frontend/src/lib/openapi/models/Tutorial.ts index ac0512ad..8e62e1ad 100644 --- a/frontend/src/lib/openapi/models/Tutorial.ts +++ b/frontend/src/lib/openapi/models/Tutorial.ts @@ -2,7 +2,11 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ +import type { Link } from './Link'; export type Tutorial = { - title: string; + header: string | null; + title: string | null; + summary: string | null; + links: Array | null; }; diff --git a/frontend/src/lib/openapi/services/DefaultService.ts b/frontend/src/lib/openapi/services/DefaultService.ts index 8f45111f..57d7272c 100644 --- a/frontend/src/lib/openapi/services/DefaultService.ts +++ b/frontend/src/lib/openapi/services/DefaultService.ts @@ -231,7 +231,7 @@ export class DefaultService { * @returns Tutorial Successful Response * @throws ApiError */ - public static getAllTutorials(): CancelablePromise> { + public static getAllTutorials(): CancelablePromise<(Array | null)> { return __request(OpenAPI, { method: 'GET', url: '/tutorials', diff --git a/frontend/src/routes/Tutorials.svelte b/frontend/src/routes/Tutorials.svelte index fb27f3b0..7712e5a5 100644 --- a/frontend/src/routes/Tutorials.svelte +++ b/frontend/src/routes/Tutorials.svelte @@ -3,19 +3,60 @@ import { Backend, type Tutorial } from "../lib/backend"; let tutorials: Tutorial[] = []; + + onMount(async () => { tutorials = await Backend.getAllTutorials(); }); -
- {#each tutorials as tutorial} -
-

{tutorial.title}

-
- {/each} +
+
+ + {#each tutorials as tutorial} +
+ + +

{tutorial.title}

+

{tutorial.summary}

+ + + + + + + + + +
+ {/each} +
From c576e6a0ace2e00946d909dad547880d518d8f7a Mon Sep 17 00:00:00 2001 From: xnought Date: Tue, 5 Mar 2024 21:26:03 -0800 Subject: [PATCH 02/67] feat: fuzzy search protein name --- backend/init.sql | 2 ++ backend/src/api/search.py | 41 ++++++++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/backend/init.sql b/backend/init.sql index 5d505c5e..e8a273da 100644 --- a/backend/init.sql +++ b/backend/init.sql @@ -11,6 +11,8 @@ -- Generated columns: -- https://www.postgresql.org/docs/current/ddl-generated-columns.html +CREATE EXTENSION pg_trgm; -- for trigram matching fuzzy search similarity() func + /* * Species Table */ diff --git a/backend/src/api/search.py b/backend/src/api/search.py index b35488d7..19924582 100644 --- a/backend/src/api/search.py +++ b/backend/src/api/search.py @@ -74,14 +74,16 @@ def get_descriptions(protein_names: list[str]): def gen_sql_filters( + species_table: str, + proteins_table: str, species_filter: str | None, length_filter: RangeFilter | None = None, mass_filter: RangeFilter | None = None, ) -> str: filters = [ - category_where_clause("species.name", species_filter), - range_where_clause("proteins.length", length_filter), - range_where_clause("proteins.mass", mass_filter), + category_where_clause(f"{species_table}.name", species_filter), + range_where_clause(f"{proteins_table}.length", length_filter), + range_where_clause(f"{proteins_table}.mass", mass_filter), ] return " AND " + combine_where_clauses(filters) if any(filters) else "" @@ -92,22 +94,33 @@ def search_proteins(body: SearchProteinsBody): with Database() as db: try: filter_clauses = gen_sql_filters( - body.species_filter, body.length_filter, body.mass_filter + "species", + "proteins_scores", + body.species_filter, + body.length_filter, + body.mass_filter, ) - entries_query = """SELECT proteins.name, - proteins.description, - proteins.length, - proteins.mass, + score_threshold = ( + "proteins_scores.name_score > 0" # show only the scores > 0 + if len(title_query) > 0 + else "TRUE" # show all scores + ) + entries_query = """SELECT proteins_scores.name, + proteins_scores.description, + proteins_scores.length, + proteins_scores.mass, species.name, - proteins.thumbnail - FROM proteins - JOIN species ON species.id = proteins.species_id - WHERE proteins.name ILIKE %s""" + proteins_scores.thumbnail + FROM (SELECT *, similarity(name, %s) as name_score FROM proteins) as proteins_scores + JOIN species ON species.id = proteins_scores.species_id + WHERE {} {} + ORDER BY proteins_scores.name_score DESC; + """.format(score_threshold, filter_clauses) log.warn(filter_clauses) entries_result = db.execute_return( - sanitize_query(entries_query + filter_clauses), + sanitize_query(entries_query), [ - f"%{title_query}%", + title_query, ], ) if entries_result is not None: From fe71e06ed5643b813b61e9d1433f6edefcfa72ef Mon Sep 17 00:00:00 2001 From: xnought Date: Tue, 5 Mar 2024 21:30:18 -0800 Subject: [PATCH 03/67] feat: update threshold --- backend/src/api/search.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/src/api/search.py b/backend/src/api/search.py index 19924582..86d72767 100644 --- a/backend/src/api/search.py +++ b/backend/src/api/search.py @@ -100,8 +100,9 @@ def search_proteins(body: SearchProteinsBody): body.length_filter, body.mass_filter, ) - score_threshold = ( - "proteins_scores.name_score > 0" # show only the scores > 0 + threshold = 0.05 + score_filter = ( + f"proteins_scores.name_score >= {threshold}" # show only the scores > 0 if len(title_query) > 0 else "TRUE" # show all scores ) @@ -115,7 +116,7 @@ def search_proteins(body: SearchProteinsBody): JOIN species ON species.id = proteins_scores.species_id WHERE {} {} ORDER BY proteins_scores.name_score DESC; - """.format(score_threshold, filter_clauses) + """.format(score_filter, filter_clauses) log.warn(filter_clauses) entries_result = db.execute_return( sanitize_query(entries_query), From c6baabee071012a1a03b3a9b1706da50416ee3cb Mon Sep 17 00:00:00 2001 From: xnought Date: Tue, 5 Mar 2024 21:37:56 -0800 Subject: [PATCH 04/67] feat: name and description fuzzy search --- backend/src/api/search.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/backend/src/api/search.py b/backend/src/api/search.py index 86d72767..b4b2eed9 100644 --- a/backend/src/api/search.py +++ b/backend/src/api/search.py @@ -90,7 +90,7 @@ def gen_sql_filters( @router.post("/search/proteins", response_model=SearchProteinsResults) def search_proteins(body: SearchProteinsBody): - title_query = sanitize_query(body.query) + text_query = sanitize_query(body.query) with Database() as db: try: filter_clauses = gen_sql_filters( @@ -102,8 +102,8 @@ def search_proteins(body: SearchProteinsBody): ) threshold = 0.05 score_filter = ( - f"proteins_scores.name_score >= {threshold}" # show only the scores > 0 - if len(title_query) > 0 + f"proteins_scores.name_score >= {threshold} OR proteins_scores.desc_score >= {threshold}" # show only the scores > 0 + if len(text_query) > 0 else "TRUE" # show all scores ) entries_query = """SELECT proteins_scores.name, @@ -112,17 +112,15 @@ def search_proteins(body: SearchProteinsBody): proteins_scores.mass, species.name, proteins_scores.thumbnail - FROM (SELECT *, similarity(name, %s) as name_score FROM proteins) as proteins_scores + FROM (SELECT *, similarity(name, %s) as name_score, similarity(description, %s) as desc_score FROM proteins) as proteins_scores JOIN species ON species.id = proteins_scores.species_id WHERE {} {} - ORDER BY proteins_scores.name_score DESC; + ORDER BY (proteins_scores.name_score + proteins_scores.desc_score) DESC; """.format(score_filter, filter_clauses) log.warn(filter_clauses) entries_result = db.execute_return( sanitize_query(entries_query), - [ - title_query, - ], + [text_query, text_query], ) if entries_result is not None: return SearchProteinsResults( From 5ba3e7019db688ccbd62bbbfb03f3eb953f223d4 Mon Sep 17 00:00:00 2001 From: xnought Date: Tue, 5 Mar 2024 22:00:04 -0800 Subject: [PATCH 05/67] feat: search over title, description, and text --- backend/init.sql | 4 ++-- backend/src/api/protein.py | 12 ++++-------- backend/src/api/search.py | 20 ++++++++++++++------ galaxy/.gitignore | 1 + 4 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 galaxy/.gitignore diff --git a/backend/init.sql b/backend/init.sql index e8a273da..02b29458 100644 --- a/backend/init.sql +++ b/backend/init.sql @@ -30,8 +30,8 @@ CREATE TABLE proteins ( description text, length integer, -- length of amino acid sequence mass numeric, -- mass in amu/daltons - content bytea, -- stored markdown for the protein article (TODO: consider having a limit to how big this can be) - refs bytea, -- bibtex references mentioned in the content/article + content text, -- stored markdown for the protein article (TODO: consider having a limit to how big this can be) + refs text, -- bibtex references mentioned in the content/article species_id integer NOT NULL, thumbnail bytea, -- thumbnail image of the protein in base64 format FOREIGN KEY (species_id) REFERENCES species(id) ON UPDATE CASCADE ON DELETE CASCADE diff --git a/backend/src/api/protein.py b/backend/src/api/protein.py index f35f9a3e..2791af40 100644 --- a/backend/src/api/protein.py +++ b/backend/src/api/protein.py @@ -164,10 +164,6 @@ def get_protein_entry(protein_name: str): ) = only_returned_entry # if byte arrays are present, decode them into a string - if content is not None: - content = bytea_to_str(content) - if refs is not None: - refs = bytea_to_str(refs) if thumbnail is not None: thumbnail = bytea_to_str(thumbnail) @@ -263,8 +259,8 @@ def upload_protein_entry(body: UploadBody, req: Request): body.description, pdb.num_amino_acids, pdb.mass_daltons, - str_to_bytea(body.content), - str_to_bytea(body.refs), + body.content, + body.refs, body.species_name, ], ) @@ -310,7 +306,7 @@ def edit_protein_entry(body: EditBody, req: Request): db.execute( """UPDATE proteins SET content = %s WHERE name = %s""", [ - str_to_bytea(body.new_content), + body.new_content, body.old_name if not name_changed else body.new_name, ], ) @@ -319,7 +315,7 @@ def edit_protein_entry(body: EditBody, req: Request): db.execute( """UPDATE proteins SET refs = %s WHERE name = %s""", [ - str_to_bytea(body.new_refs), + body.new_refs, body.old_name if not name_changed else body.new_name, ], ) diff --git a/backend/src/api/search.py b/backend/src/api/search.py index b4b2eed9..5f0bc15e 100644 --- a/backend/src/api/search.py +++ b/backend/src/api/search.py @@ -100,27 +100,35 @@ def search_proteins(body: SearchProteinsBody): body.length_filter, body.mass_filter, ) - threshold = 0.05 + threshold = 0 score_filter = ( - f"proteins_scores.name_score >= {threshold} OR proteins_scores.desc_score >= {threshold}" # show only the scores > 0 + f"(proteins_scores.name_score >= {threshold} OR proteins_scores.desc_score >= {threshold} OR proteins_scores.content_score >= {threshold})" # show only the scores > 0 if len(text_query) > 0 else "TRUE" # show all scores ) + # cursed shit, edit this at some point + # note that we have a sub query since postgres can't do where clauses on aliased tables entries_query = """SELECT proteins_scores.name, proteins_scores.description, proteins_scores.length, proteins_scores.mass, species.name, proteins_scores.thumbnail - FROM (SELECT *, similarity(name, %s) as name_score, similarity(description, %s) as desc_score FROM proteins) as proteins_scores + FROM (SELECT *, + similarity(name, %s) as name_score, + similarity(description, %s) as desc_score, + similarity(content, %s) as content_score + FROM proteins) as proteins_scores JOIN species ON species.id = proteins_scores.species_id WHERE {} {} - ORDER BY (proteins_scores.name_score + proteins_scores.desc_score) DESC; - """.format(score_filter, filter_clauses) + ORDER BY (proteins_scores.name_score*4 + proteins_scores.desc_score*2 + proteins_scores.content_score) DESC; + """.format( + score_filter, filter_clauses + ) # numbers in order by correspond to weighting log.warn(filter_clauses) entries_result = db.execute_return( sanitize_query(entries_query), - [text_query, text_query], + [text_query, text_query, text_query], ) if entries_result is not None: return SearchProteinsResults( diff --git a/galaxy/.gitignore b/galaxy/.gitignore new file mode 100644 index 00000000..6af1f0bd --- /dev/null +++ b/galaxy/.gitignore @@ -0,0 +1 @@ +master_venom_galaxy/ \ No newline at end of file From daac880bde42df6e455a9c28a8b38077e772bb1a Mon Sep 17 00:00:00 2001 From: Cooper Bailey Date: Sun, 10 Mar 2024 11:59:50 -0700 Subject: [PATCH 06/67] working on tutorials tables --- backend/init.sql | 29 ++++++++++--- backend/src/api/tutorials.py | 46 ++++++--------------- backend/src/setup.py | 2 + frontend/src/lib/openapi/models/Tutorial.ts | 4 +- frontend/src/routes/Tutorials.svelte | 9 ++-- 5 files changed, 47 insertions(+), 43 deletions(-) diff --git a/backend/init.sql b/backend/init.sql index 5d505c5e..deffc64b 100644 --- a/backend/init.sql +++ b/backend/init.sql @@ -25,7 +25,7 @@ CREATE TABLE species ( CREATE TABLE proteins ( id serial PRIMARY KEY, name text NOT NULL UNIQUE, -- user specified name of the protein (TODO: consider having a string limit) - description text, + description text, length integer, -- length of amino acid sequence mass numeric, -- mass in amu/daltons content bytea, -- stored markdown for the protein article (TODO: consider having a limit to how big this can be) @@ -46,12 +46,24 @@ CREATE TABLE users ( admin boolean NOT NULL ); + +/* +* Tutorials Table +*/ +CREATE TABLE tutorials ( + id serial PRIMARY KEY, + header text NOT NULL, + title text, + description text +); + + /* * Inserts example species into species table */ -INSERT INTO species(name) VALUES ('ganaspis hookeri'); -INSERT INTO species(name) VALUES ('leptopilina boulardi'); -INSERT INTO species(name) VALUES ('leptopilina heterotoma'); +INSERT INTO species(name) VALUES ('ganaspis hookeri'); +INSERT INTO species(name) VALUES ('leptopilina boulardi'); +INSERT INTO species(name) VALUES ('leptopilina heterotoma'); INSERT INTO species(name) VALUES ('unknown'); /* @@ -59,4 +71,11 @@ INSERT INTO species(name) VALUES ('unknown'); * Email: test * Password: test */ -INSERT INTO users(username, email, pword, admin) VALUES ('test', 'test', '$2b$12$PFoNf7YM0KNIPP8WGsJjveIOhiJjitsMtfwRcCjdcyTuqjdk/q//u', '1'); \ No newline at end of file +INSERT INTO users(username, email, pword, admin) VALUES ('test', 'test', '$2b$12$PFoNf7YM0KNIPP8WGsJjveIOhiJjitsMtfwRcCjdcyTuqjdk/q//u', '1'); + + +INSERT INTO tutorials(header, title, description) VALUES('Tutorial Page', 'General Informtion', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce gravida tristique est, a sollicitudin nulla varius ac. Duis sed lacus arcu. Mauris eget condimentum justo. Vestibulum iaculis cursus accumsan. Mauris eget diam consequat, viverra dui malesuada, maximus nisl. Etiam laoreet venenatis odio ut tempus. Praesent risus est, eleifend id purus non, varius rutrum nisi. Fusce sagittis lorem nec tristique efficitur. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam pretium, sapien et volutpat tempus, elit urna congue massa, id consequat leo dolor in ligula. Sed vestibulum tristique eros eu aliquet.'); +INSERT INTO tutorials(header, title, description) VALUES('Tutorial 1', 'General Informtion', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); +INSERT INTO tutorials(header, title, description) VALUES('Tutorial 2', 'General Informtion', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); +INSERT INTO tutorials(header, title, description) VALUES('Tutorial 3', 'General Informtion', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'); + diff --git a/backend/src/api/tutorials.py b/backend/src/api/tutorials.py index 6f78ebd9..7784b73c 100644 --- a/backend/src/api/tutorials.py +++ b/backend/src/api/tutorials.py @@ -1,6 +1,7 @@ from fastapi import APIRouter from ..api_types import CamelModel -# from ..db import Database +from ..db import Database +import logging as log router = APIRouter() @@ -12,8 +13,8 @@ class Link(CamelModel): class Tutorial(CamelModel): header: str | None = None title: str | None = None - summary: str | None = None - links: list[Link] | None = None + description: str | None = None + # links: list[Link] | None = None class MultipleTutorials(CamelModel): tutorials: list[Tutorial] | None = None @@ -24,34 +25,13 @@ class MultipleTutorials(CamelModel): @router.get("/tutorials", response_model=list[Tutorial]) def get_all_tutorials(): - return [ - Tutorial(title="Tutorial Page", summary="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce gravida tristique est, a sollicitudin nulla varius ac. Duis sed lacus arcu. Mauris eget condimentum justo. Vestibulum iaculis cursus accumsan. Mauris eget diam consequat, viverra dui malesuada, maximus nisl. Etiam laoreet venenatis odio ut tempus. Praesent risus est, eleifend id purus non, varius rutrum nisi. Fusce sagittis lorem nec tristique efficitur. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam pretium, sapien et volutpat tempus, elit urna congue massa, id consequat leo dolor in ligula. Sed vestibulum tristique eros eu aliquet."), - Tutorial(title="Tutorial 2", summary="Future links to second tutorial page", links=[ - Link(url="url1", title="Link 1"), - Link(url="url2", title="Link 2"), - ] - ), - Tutorial(title="Tutorial 3", summary="Future links to third tutorial page", links=[ - Link(url="url3", title="Link 3"), - Link(url="url4", title="Link 4"), - ] - ), - Tutorial(title="Tutorial 4", summary="Future links to forth tutorial page", links=[ - Link(url="url5", title="Link 5"), - Link(url="url6", title="Link 6"), - ] - ) - ] - - - # try: - # with Database() as db: - # query = """SELECT name as species_name FROM species""" - # entry_sql = db.execute_return(query) - # if entry_sql is not None: - # for d in entry_sql: - - # return [d[0] for d in entry_sql] - # except Exception: - # return + with Database() as db: + try: + query = """SELECT header, title, description FROM tutorials""" + entries = db.execute_return(query) + if entries: + tutorials = [Tutorial(**entry) for entry in entries] + return tutorials + except Exception as e: + log.error(e) diff --git a/backend/src/setup.py b/backend/src/setup.py index 96083991..b4c17287 100644 --- a/backend/src/setup.py +++ b/backend/src/setup.py @@ -30,7 +30,9 @@ def custom_generate_unique_id(route: APIRoute): def init_fastapi_app() -> FastAPI: + app = FastAPI( title="Venome Backend", generate_unique_id_function=custom_generate_unique_id ) + app = disable_cors(app) return app diff --git a/frontend/src/lib/openapi/models/Tutorial.ts b/frontend/src/lib/openapi/models/Tutorial.ts index 8e62e1ad..cd69a3c4 100644 --- a/frontend/src/lib/openapi/models/Tutorial.ts +++ b/frontend/src/lib/openapi/models/Tutorial.ts @@ -6,7 +6,7 @@ import type { Link } from './Link'; export type Tutorial = { header: string | null; title: string | null; - summary: string | null; - links: Array | null; + description: string | null; + // links: Array | null; }; diff --git a/frontend/src/routes/Tutorials.svelte b/frontend/src/routes/Tutorials.svelte index 7712e5a5..f852bb96 100644 --- a/frontend/src/routes/Tutorials.svelte +++ b/frontend/src/routes/Tutorials.svelte @@ -3,10 +3,13 @@ import { Backend, type Tutorial } from "../lib/backend"; let tutorials: Tutorial[] = []; - + let error = false; onMount(async () => { tutorials = await Backend.getAllTutorials(); + if (tutorials == null) error = true; + + console.log("Received", tutorials); }); @@ -18,7 +21,7 @@

{tutorial.title}

-

{tutorial.summary}

+

{tutorial.description}

@@ -55,7 +58,7 @@ } .summary { - font-size: 18px; + font-size: 18px; margin-left: 40px; margin-right: 40px; } From ac36b17ac02497c6bb267976467cb98b362cc757 Mon Sep 17 00:00:00 2001 From: ansengarvin <45224464+ansengarvin@users.noreply.github.com> Date: Sun, 10 Mar 2024 13:23:30 -0700 Subject: [PATCH 07/67] Protein Comparison via Model Overlaying using TM Align (#192) * Feat: Compare endpoint which returns a TM-Aligned PDB file. * Fix: Removed useless overlay test route * Created incomplete create page. * Fix: Changed protein compare from post to get to play nicer with Molstar * Changed /compare to /pdb to play nicer with existing code * Created rudimentary overlap page * Made viewport larger for this demo * Added ability to download tm align to docker via shell command * Added links on similarity search to redirect user to comparison page * Linting pass with Ruff * Format: Removed unused local variables * Format: Removed unused imports * Format: Commented out stdout debug print * Format: Ruff pass on protein.py * fix: uncomment run tmalign * fix: add use:link to a tag --------- Co-authored-by: xnought --- backend/.gitignore | 3 +- backend/src/api/protein.py | 17 +++++ backend/src/api_types.py | 9 +++ backend/src/tmalign.py | 75 +++++++++++++++++++++ frontend/src/Router.svelte | 2 + frontend/src/lib/SimilarProteins.svelte | 8 +++ frontend/src/routes/Compare.svelte | 90 +++++++++++++++++++++++++ run.sh | 14 ++++ 8 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 backend/src/tmalign.py create mode 100644 frontend/src/routes/Compare.svelte diff --git a/backend/.gitignore b/backend/.gitignore index 912e757d..b9a0cc6c 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,4 @@ __pycache__/ .venv -foldseek/ \ No newline at end of file +foldseek/ +tmalign/ \ No newline at end of file diff --git a/backend/src/api/protein.py b/backend/src/api/protein.py index 2791af40..82865ec0 100644 --- a/backend/src/api/protein.py +++ b/backend/src/api/protein.py @@ -5,9 +5,11 @@ from Bio.PDB import PDBParser from Bio.SeqUtils import molecular_weight, seq1 from ..db import Database, bytea_to_str, str_to_bytea +from fastapi.exceptions import HTTPException from ..api_types import ProteinEntry, UploadBody, UploadError, EditBody, CamelModel from ..auth import requiresAuthentication +from ..tmalign import tm_align from io import BytesIO from fastapi import APIRouter from fastapi.responses import FileResponse, StreamingResponse @@ -331,3 +333,18 @@ def edit_protein_entry(body: EditBody, req: Request): except Exception: return UploadError.WRITE_ERROR + + +# /pdb with two attributes returns both PDBs, superimposed and with different colors. +@router.get("/protein/pdb/{proteinA:str}/{proteinB:str}") +def search_proteins(proteinA: str, proteinB: str): + try: + pdbA = stored_pdb_file_name(proteinA) + pdbB = stored_pdb_file_name(proteinB) + + file = tm_align(proteinA, pdbA, proteinB, pdbB) + + return FileResponse(file, filename=proteinA + "_" + proteinB + ".pdb") + except Exception as e: + log.error(e) + raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/src/api_types.py b/backend/src/api_types.py index c15061e1..55346786 100644 --- a/backend/src/api_types.py +++ b/backend/src/api_types.py @@ -76,3 +76,12 @@ class LoginBody(CamelModel): class LoginResponse(CamelModel): token: str error: str + + +class CompareBody(CamelModel): + proteinA: str + proteinB: str + + +class CompareResponse(CamelModel): + file: list[str] diff --git a/backend/src/tmalign.py b/backend/src/tmalign.py new file mode 100644 index 00000000..3790a329 --- /dev/null +++ b/backend/src/tmalign.py @@ -0,0 +1,75 @@ +import subprocess +import logging as log +import os + + +def bash_cmd(cmd: str | list[str]) -> str: + return subprocess.check_output(cmd, shell=True).decode() + + +TMALIGN_LOCATION = "/app/tmalign" +TMALIGN_EXECUTABLE = f"{TMALIGN_LOCATION}/tmalign" + + +def assert_tmalign_installed(): + if os.path.exists(TMALIGN_EXECUTABLE): + return + else: + raise ImportError( + "tm align executable not installed. Please install manually - Automatic install TODO." + ) + + +def parse_pdb(filepath: str) -> list[str]: + with open(filepath, "r") as f: + lines = f.readlines() + return lines + + +def tm_align( + protein_A: str, pdbA: str, protein_B: str, pdbB: str, type: str = "_all_atm" +): + """ + Description: + Returns two overlaid, aligned, and colored PDB structures in a single PDB file. + The ones without extensions appear to be PDB files. + + Params: + protein_A: + The name of the first protein. + pdbA: + The filepath of the first protein. + protein_B: + The name of the second protein. + pdbB: + The filepath of the second protein. + type: + The kind of file you want. Experiment with these! Defaults to _all_atm, + which shows alpha helices and beta sheets. Valid options include: + "", "_all", "_all_atm", "_all_atm_lig", "_atm", + ".pml", "_all.pml", "_all_atm.pml", "all_atm_lig.pml", "_atm.pml" + """ + dir_name = protein_A + "-" + protein_B + full_path = f"{TMALIGN_LOCATION}/{dir_name}" + out_file = full_path + "/output" + desired_file = out_file + type + + # If the directory already exists, then we've already run TM align for this protein pair. We can just return the file. + if os.path.exists(full_path): + log.warn(f"Path {full_path} already exists. Do not need to run TM align.") + + # If the directory doesn't exist, then we need to run TM align and generate the files. + else: + log.warn(f"Path {full_path} does not exist. Creating directory and returning.") + cmd = f"{TMALIGN_EXECUTABLE} {pdbA} {pdbB} -o {out_file}" + try: + bash_cmd(f"mkdir {full_path}") + log.warn(f"Attempting to align now with cmd {cmd}") + stdout = bash_cmd(cmd) + log.warn(stdout) + + except Exception as e: + log.warn(e) + raise e + + return desired_file diff --git a/frontend/src/Router.svelte b/frontend/src/Router.svelte index c22c1d5b..3c0016ad 100644 --- a/frontend/src/Router.svelte +++ b/frontend/src/Router.svelte @@ -10,6 +10,7 @@ import Edit from "./routes/Edit.svelte"; import Tutorials from "./routes/Tutorials.svelte"; import ForceUploadThumbnails from "./routes/ForceUploadThumbnails.svelte"; + import Compare from "./routes/Compare.svelte"; @@ -27,6 +28,7 @@ > + diff --git a/frontend/src/lib/SimilarProteins.svelte b/frontend/src/lib/SimilarProteins.svelte index a7d797f7..b5e24f74 100644 --- a/frontend/src/lib/SimilarProteins.svelte +++ b/frontend/src/lib/SimilarProteins.svelte @@ -28,6 +28,7 @@ Probability Match E-Value Description + Compare {#each similarProteins as protein} @@ -41,6 +42,13 @@ {protein.prob} {protein.evalue} {protein.description} + + Compare + {/each} diff --git a/frontend/src/routes/Compare.svelte b/frontend/src/routes/Compare.svelte new file mode 100644 index 00000000..b1866b05 --- /dev/null +++ b/frontend/src/routes/Compare.svelte @@ -0,0 +1,90 @@ + + + + Venome Protein {entry ? entry.name : ""} + + +
+ {#if entry} +
+ +

+ Comparing Proteins +

+ +
+ {proteinA} and {proteinB} +
+ { + // upload the protein thumbnail if it doesn't exist + if (entry !== null && entry.thumbnail === null) { + const b64 = await screenshot(); + const res = await Backend.uploadProteinPng({ + proteinName: entry.name, + base64Encoding: b64, + }); + } + }} + /> +
+ {:else if !error} + +

+ {:else if error} + +

Error

+

Could not find a protein with the id {urlId}

+ {/if} +
+ + diff --git a/run.sh b/run.sh index 89ce2c4f..c49310da 100755 --- a/run.sh +++ b/run.sh @@ -115,6 +115,20 @@ function remove_foldseek() { docker exec -it venome-backend rm -fr foldseek/ } +function add_tmalign() { + docker exec -it venome-backend wget https://seq2fun.dcmb.med.umich.edu//TM-align/TMalign_cpp.gz + docker exec -it venome-backend mkdir tmalign + docker exec -it venome-backend gzip -d TMalign_cpp.gz + docker exec -it venome-backend mv TMalign_cpp tmalign/tmalign + docker exec -it venome-backend chmod a+x tmalign/tmalign + docker exec -it venome-backend rm -f TMalign_cpp.gz +} + +function remove_tmalign() { + docker exec -it venome-backend rm -f TMalign_cpp.gz* + docker exec -it venome-backend rm -fr tmalign/ +} + function scrape_func_names() { functions=($(grep -oE 'function[[:space:]]+[a-zA-Z_][a-zA-Z_0-9]*' ./run.sh | sed 's/function[[:space:]]*//')) } From 080c8c5ba3e8d57b6f98991d27c25983d21c8247 Mon Sep 17 00:00:00 2001 From: ansengarvin <45224464+ansengarvin@users.noreply.github.com> Date: Sun, 10 Mar 2024 14:10:18 -0700 Subject: [PATCH 08/67] Fixed various backend issues causing /tutorials API endpoint to not load --- backend/src/api/search.py | 1 + backend/src/api/tutorials.py | 16 +++++++++------- backend/src/setup.py | 1 - frontend/src/lib/openapi/index.ts | 1 + .../models/{Link.ts => MultipleTutorials.ts} | 6 +++--- frontend/src/lib/openapi/models/ProteinEntry.ts | 17 +++++++++-------- frontend/src/lib/openapi/models/Tutorial.ts | 8 +++----- .../src/lib/openapi/services/DefaultService.ts | 6 +++--- frontend/src/routes/Tutorials.svelte | 9 ++++++--- ...ts.timestamp-1710099491738-507fb678492d4.mjs | 10 ++++++++++ 10 files changed, 45 insertions(+), 30 deletions(-) rename frontend/src/lib/openapi/models/{Link.ts => MultipleTutorials.ts} (53%) create mode 100644 frontend/vite.config.ts.timestamp-1710099491738-507fb678492d4.mjs diff --git a/backend/src/api/search.py b/backend/src/api/search.py index 4e80fab5..9f1d8301 100644 --- a/backend/src/api/search.py +++ b/backend/src/api/search.py @@ -89,6 +89,7 @@ def search_proteins(body: SearchProteinsBody): ], ) if entries_result is not None: + print(entries_result) return SearchProteinsResults( protein_entries=[ ProteinEntry( diff --git a/backend/src/api/tutorials.py b/backend/src/api/tutorials.py index 7784b73c..a5956612 100644 --- a/backend/src/api/tutorials.py +++ b/backend/src/api/tutorials.py @@ -10,28 +10,30 @@ class Link(CamelModel): url: str title: str + class Tutorial(CamelModel): header: str | None = None title: str | None = None description: str | None = None # links: list[Link] | None = None + class MultipleTutorials(CamelModel): tutorials: list[Tutorial] | None = None - - - -@router.get("/tutorials", response_model=list[Tutorial]) +@router.get("/tutorials", response_model=MultipleTutorials | None) def get_all_tutorials(): with Database() as db: try: query = """SELECT header, title, description FROM tutorials""" entries = db.execute_return(query) if entries: - tutorials = [Tutorial(**entry) for entry in entries] - return tutorials + return MultipleTutorials( + tutorials=[ + Tutorial(header=header, title=title, description=description) + for header, title, description in entries + ] + ) except Exception as e: log.error(e) - diff --git a/backend/src/setup.py b/backend/src/setup.py index b4c17287..7caf927e 100644 --- a/backend/src/setup.py +++ b/backend/src/setup.py @@ -30,7 +30,6 @@ def custom_generate_unique_id(route: APIRoute): def init_fastapi_app() -> FastAPI: - app = FastAPI( title="Venome Backend", generate_unique_id_function=custom_generate_unique_id ) diff --git a/frontend/src/lib/openapi/index.ts b/frontend/src/lib/openapi/index.ts index 373c870b..9d953bfd 100644 --- a/frontend/src/lib/openapi/index.ts +++ b/frontend/src/lib/openapi/index.ts @@ -11,6 +11,7 @@ export type { EditBody } from './models/EditBody'; export type { HTTPValidationError } from './models/HTTPValidationError'; export type { LoginBody } from './models/LoginBody'; export type { LoginResponse } from './models/LoginResponse'; +export type { MultipleTutorials } from './models/MultipleTutorials'; export type { ProteinEntry } from './models/ProteinEntry'; export type { RangeFilter } from './models/RangeFilter'; export type { SearchProteinsBody } from './models/SearchProteinsBody'; diff --git a/frontend/src/lib/openapi/models/Link.ts b/frontend/src/lib/openapi/models/MultipleTutorials.ts similarity index 53% rename from frontend/src/lib/openapi/models/Link.ts rename to frontend/src/lib/openapi/models/MultipleTutorials.ts index 97415b0c..27c1e2ef 100644 --- a/frontend/src/lib/openapi/models/Link.ts +++ b/frontend/src/lib/openapi/models/MultipleTutorials.ts @@ -2,8 +2,8 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -export type Link = { - url: string; - title: string; +import type { Tutorial } from './Tutorial'; +export type MultipleTutorials = { + tutorials?: (Array | null); }; diff --git a/frontend/src/lib/openapi/models/ProteinEntry.ts b/frontend/src/lib/openapi/models/ProteinEntry.ts index d375cabb..a25dd470 100644 --- a/frontend/src/lib/openapi/models/ProteinEntry.ts +++ b/frontend/src/lib/openapi/models/ProteinEntry.ts @@ -3,12 +3,13 @@ /* tslint:disable */ /* eslint-disable */ export type ProteinEntry = { - name: string; - length: number; - mass: number; - speciesName: string; - content?: string | null; - refs?: string | null; - thumbnail?: string | null; - description?: string | null; + name: string; + length: number; + mass: number; + speciesName: string; + content?: (string | null); + refs?: (string | null); + thumbnail?: (string | null); + description?: (string | null); }; + diff --git a/frontend/src/lib/openapi/models/Tutorial.ts b/frontend/src/lib/openapi/models/Tutorial.ts index cd69a3c4..d35904c6 100644 --- a/frontend/src/lib/openapi/models/Tutorial.ts +++ b/frontend/src/lib/openapi/models/Tutorial.ts @@ -2,11 +2,9 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { Link } from './Link'; export type Tutorial = { - header: string | null; - title: string | null; - description: string | null; - // links: Array | null; + header?: (string | null); + title?: (string | null); + description?: (string | null); }; diff --git a/frontend/src/lib/openapi/services/DefaultService.ts b/frontend/src/lib/openapi/services/DefaultService.ts index 57d7272c..cb3c9924 100644 --- a/frontend/src/lib/openapi/services/DefaultService.ts +++ b/frontend/src/lib/openapi/services/DefaultService.ts @@ -5,10 +5,10 @@ import type { EditBody } from '../models/EditBody'; import type { LoginBody } from '../models/LoginBody'; import type { LoginResponse } from '../models/LoginResponse'; +import type { MultipleTutorials } from '../models/MultipleTutorials'; import type { ProteinEntry } from '../models/ProteinEntry'; import type { SearchProteinsBody } from '../models/SearchProteinsBody'; import type { SearchProteinsResults } from '../models/SearchProteinsResults'; -import type { Tutorial } from '../models/Tutorial'; import type { UploadBody } from '../models/UploadBody'; import type { UploadError } from '../models/UploadError'; import type { UploadPNGBody } from '../models/UploadPNGBody'; @@ -228,10 +228,10 @@ export class DefaultService { } /** * Get All Tutorials - * @returns Tutorial Successful Response + * @returns any Successful Response * @throws ApiError */ - public static getAllTutorials(): CancelablePromise<(Array | null)> { + public static getAllTutorials(): CancelablePromise<(MultipleTutorials | null)> { return __request(OpenAPI, { method: 'GET', url: '/tutorials', diff --git a/frontend/src/routes/Tutorials.svelte b/frontend/src/routes/Tutorials.svelte index f852bb96..b00691f5 100644 --- a/frontend/src/routes/Tutorials.svelte +++ b/frontend/src/routes/Tutorials.svelte @@ -1,12 +1,15 @@ + +
+ + diff --git a/frontend/src/lib/ProteinVis.svelte b/frontend/src/lib/ProteinVis.svelte deleted file mode 100644 index 98943dee..00000000 --- a/frontend/src/lib/ProteinVis.svelte +++ /dev/null @@ -1,88 +0,0 @@ - - -
- - diff --git a/frontend/src/routes/Compare.svelte b/frontend/src/routes/Compare.svelte index b1866b05..324f5a04 100644 --- a/frontend/src/routes/Compare.svelte +++ b/frontend/src/routes/Compare.svelte @@ -1,31 +1,26 @@  + + + + + {qend} + {qstart} + diff --git a/frontend/src/lib/SimilarProteins.svelte b/frontend/src/lib/SimilarProteins.svelte index 76241f9c..6815744c 100644 --- a/frontend/src/lib/SimilarProteins.svelte +++ b/frontend/src/lib/SimilarProteins.svelte @@ -4,14 +4,17 @@ import { onMount } from "svelte"; import { Backend, type SimilarProtein } from "../lib/backend"; import { undoFormatProteinName } from "./format"; + import AlignBlock from "./AlignBlock.svelte"; export let queryProteinName: string; + export let length: number; let similarProteins: SimilarProtein[] = []; onMount(async () => { try { similarProteins = await Backend.searchVenomeSimilar(queryProteinName); + console.log(similarProteins); } catch (e) { console.error(e); console.error( @@ -27,21 +30,29 @@ Name Probability Match E-Value - Alignment - 3D Superimpose TMAlign + Alignment Coverage + TMAlign - {#each similarProteins as protein} + {#each similarProteins as protein, i} - + {undoFormatProteinName(protein.name)} - {protein.prob} - {protein.evalue.toExponential()} - blah blah + {protein.prob} + {protein.evalue.toExponential()} + + Structurally Similar Proteins {#if entry.name} - + {/if}
diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 835d3a1f..b4a5ab46 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2871,6 +2871,11 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== +commander@7: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + commander@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" @@ -3101,19 +3106,19 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== -"d3-array@2 - 3", "d3-array@2.10.0 - 3": +"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0: version "3.2.4" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== dependencies: internmap "1 - 2" -d3-axis@^3.0.0: +d3-axis@3, d3-axis@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-axis/-/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" integrity sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== -d3-brush@^3.0.0: +d3-brush@3, d3-brush@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-brush/-/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" integrity sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== @@ -3124,17 +3129,38 @@ d3-brush@^3.0.0: d3-selection "3" d3-transition "3" -"d3-color@1 - 3": +d3-chord@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-chord/-/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" + integrity sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== + dependencies: + d3-path "1 - 3" + +"d3-color@1 - 3", d3-color@3: version "3.1.0" resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== -"d3-dispatch@1 - 3": +d3-contour@4: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.2.tgz#bb92063bc8c5663acb2422f99c73cbb6c6ae3bcc" + integrity sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== + dependencies: + d3-array "^3.2.0" + +d3-delaunay@6: + version "6.0.4" + resolved "https://registry.yarnpkg.com/d3-delaunay/-/d3-delaunay-6.0.4.tgz#98169038733a0a5babbeda55054f795bb9e4a58b" + integrity sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== + dependencies: + delaunator "5" + +"d3-dispatch@1 - 3", d3-dispatch@3: version "3.0.1" resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== -"d3-drag@2 - 3": +"d3-drag@2 - 3", d3-drag@3: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== @@ -3142,24 +3168,89 @@ d3-brush@^3.0.0: d3-dispatch "1 - 3" d3-selection "3" -"d3-ease@1 - 3": +"d3-dsv@1 - 3", d3-dsv@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" + integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== + dependencies: + commander "7" + iconv-lite "0.6" + rw "1" + +"d3-ease@1 - 3", d3-ease@3: version "3.0.1" resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== -"d3-format@1 - 3": +d3-fetch@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-fetch/-/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" + integrity sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== + dependencies: + d3-dsv "1 - 3" + +d3-force@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" + integrity sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== + dependencies: + d3-dispatch "1 - 3" + d3-quadtree "1 - 3" + d3-timer "1 - 3" + +"d3-format@1 - 3", d3-format@3: version "3.1.0" resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== -"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3": +d3-geo@3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-3.1.1.tgz#6027cf51246f9b2ebd64f99e01dc7c3364033a4d" + integrity sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== + dependencies: + d3-array "2.5.0 - 3" + +d3-hierarchy@3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6" + integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== + +"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: version "3.0.1" resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== dependencies: d3-color "1 - 3" -d3-scale@^4.0.2: +"d3-path@1 - 3", d3-path@3, d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-polygon@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-polygon/-/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" + integrity sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== + +"d3-quadtree@1 - 3", d3-quadtree@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" + integrity sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== + +d3-random@3: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" + integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== + +d3-scale-chromatic@3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz#34c39da298b23c20e02f1a4b239bd0f22e7f1314" + integrity sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== + dependencies: + d3-color "1 - 3" + d3-interpolate "1 - 3" + +d3-scale@4, d3-scale@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== @@ -3170,31 +3261,38 @@ d3-scale@^4.0.2: d3-time "2.1.1 - 3" d3-time-format "2 - 4" -d3-selection@3, d3-selection@^3.0.0: +"d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== -"d3-time-format@2 - 4": +d3-shape@3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4", d3-time-format@4: version "4.1.0" resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== dependencies: d3-time "1 - 3" -"d3-time@1 - 3", "d3-time@2.1.1 - 3": +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: version "3.1.0" resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== dependencies: d3-array "2 - 3" -"d3-timer@1 - 3": +"d3-timer@1 - 3", d3-timer@3: version "3.0.1" resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== -d3-transition@3: +"d3-transition@2 - 3", d3-transition@3: version "3.0.1" resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== @@ -3205,6 +3303,53 @@ d3-transition@3: d3-interpolate "1 - 3" d3-timer "1 - 3" +d3-zoom@3: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== + dependencies: + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" + +d3@^7.9.0: + version "7.9.0" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.9.0.tgz#579e7acb3d749caf8860bd1741ae8d371070cd5d" + integrity sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== + dependencies: + d3-array "3" + d3-axis "3" + d3-brush "3" + d3-chord "3" + d3-color "3" + d3-contour "4" + d3-delaunay "6" + d3-dispatch "3" + d3-drag "3" + d3-dsv "3" + d3-ease "3" + d3-fetch "3" + d3-force "3" + d3-format "3" + d3-geo "3" + d3-hierarchy "3" + d3-interpolate "3" + d3-path "3" + d3-polygon "3" + d3-quadtree "3" + d3-random "3" + d3-scale "4" + d3-scale-chromatic "3" + d3-selection "3" + d3-shape "3" + d3-time "3" + d3-time-format "4" + d3-timer "3" + d3-transition "3" + d3-zoom "3" + date-fns@^2.29.1: version "2.30.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" @@ -3293,6 +3438,13 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delaunator@5: + version "5.0.1" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.1.tgz#39032b08053923e924d6094fe2cde1a99cc51278" + integrity sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== + dependencies: + robust-predicates "^3.0.2" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -4461,7 +4613,7 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@0.6.3, iconv-lite@^0.6.2: +iconv-lite@0.6, iconv-lite@0.6.3, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -6507,6 +6659,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +robust-predicates@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" + integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== + rollup@^4.2.0: version "4.9.6" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.6.tgz#4515facb0318ecca254a2ee1315e22e09efc50a0" @@ -6536,6 +6693,11 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rw@1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" + integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== + rxjs@^7.0.0, rxjs@^7.8.1: version "7.8.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" From de5a910f50b235106215907139f95b1f38971caf Mon Sep 17 00:00:00 2001 From: xnought Date: Thu, 4 Apr 2024 17:45:57 -0700 Subject: [PATCH 22/67] feat: show the values with dot --- frontend/src/lib/Dot.svelte | 24 ++++++++ frontend/src/lib/SimilarProteins.svelte | 76 +++++++++++++++++-------- 2 files changed, 77 insertions(+), 23 deletions(-) create mode 100644 frontend/src/lib/Dot.svelte diff --git a/frontend/src/lib/Dot.svelte b/frontend/src/lib/Dot.svelte new file mode 100644 index 00000000..ecf185d1 --- /dev/null +++ b/frontend/src/lib/Dot.svelte @@ -0,0 +1,24 @@ + + +
+ + + +
+ + diff --git a/frontend/src/lib/SimilarProteins.svelte b/frontend/src/lib/SimilarProteins.svelte index 6815744c..3bbf89db 100644 --- a/frontend/src/lib/SimilarProteins.svelte +++ b/frontend/src/lib/SimilarProteins.svelte @@ -5,16 +5,20 @@ import { Backend, type SimilarProtein } from "../lib/backend"; import { undoFormatProteinName } from "./format"; import AlignBlock from "./AlignBlock.svelte"; + import Dot from "./Dot.svelte"; export let queryProteinName: string; export let length: number; let similarProteins: SimilarProtein[] = []; + let maxEvalue: number; onMount(async () => { try { similarProteins = await Backend.searchVenomeSimilar(queryProteinName); - console.log(similarProteins); + maxEvalue = similarProteins + ? Math.max(...similarProteins.map((p) => p.evalue)) + : 0; } catch (e) { console.error(e); console.error( @@ -28,37 +32,63 @@ - - - + + + {#each similarProteins as protein, i} + - - + {/each} From 9f4628a797ec570f25a2e78a7109b3c964e7b7fd Mon Sep 17 00:00:00 2001 From: xnought Date: Thu, 4 Apr 2024 18:05:49 -0700 Subject: [PATCH 23/67] feat: style the table reactively --- frontend/src/lib/SimilarProteins.svelte | 34 ++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/frontend/src/lib/SimilarProteins.svelte b/frontend/src/lib/SimilarProteins.svelte index 3bbf89db..b180c01e 100644 --- a/frontend/src/lib/SimilarProteins.svelte +++ b/frontend/src/lib/SimilarProteins.svelte @@ -31,11 +31,11 @@
Name Probability Match E-Value Alignment Coverage TMAlign Prob. Match Alignment Region TMAlign Superimpose 3D
- - - {undoFormatProteinName(protein.name)} - - + + +
+ + {protein.evalue.toExponential()} +
{protein.prob}{protein.evalue.toExponential()} + >
+ + {protein.prob} +
+
+ +
- Compare +
- - - - - + + + + + {#each similarProteins as protein, i} @@ -71,7 +71,7 @@ + + + + + + + {/each} +
Name E-Value Prob. Match Alignment Region TMAlign Superimpose 3D Name E-Value Prob. Match Alignment Region TMAlign
From c11fef3e9a723948e4ff2f27d19ad4029db11738 Mon Sep 17 00:00:00 2001 From: xnought Date: Thu, 4 Apr 2024 18:29:20 -0700 Subject: [PATCH 24/67] feat: use accordian and consider false to start --- frontend/src/lib/DelayedSpinner.svelte | 7 +- frontend/src/lib/SimilarProteins.svelte | 144 +++++++++++++----------- frontend/src/routes/Protein.svelte | 21 ++-- 3 files changed, 97 insertions(+), 75 deletions(-) diff --git a/frontend/src/lib/DelayedSpinner.svelte b/frontend/src/lib/DelayedSpinner.svelte index beeacbcf..58da1461 100644 --- a/frontend/src/lib/DelayedSpinner.svelte +++ b/frontend/src/lib/DelayedSpinner.svelte @@ -4,6 +4,7 @@ export let msDelay = 500; export let spinnerProps = {}; export let text = ""; + export let textRight = false; let showSpinner = false; @@ -15,5 +16,9 @@ {#if showSpinner} - {text} + {#if textRight} + {text} + {:else} + {text} + {/if} {/if} diff --git a/frontend/src/lib/SimilarProteins.svelte b/frontend/src/lib/SimilarProteins.svelte index b180c01e..c018f323 100644 --- a/frontend/src/lib/SimilarProteins.svelte +++ b/frontend/src/lib/SimilarProteins.svelte @@ -6,11 +6,13 @@ import { undoFormatProteinName } from "./format"; import AlignBlock from "./AlignBlock.svelte"; import Dot from "./Dot.svelte"; + import DelayedSpinner from "./DelayedSpinner.svelte"; export let queryProteinName: string; export let length: number; - let similarProteins: SimilarProtein[] = []; + let similarProteins: SimilarProtein[]; + let errorEncountered = false; let maxEvalue: number; onMount(async () => { try { @@ -24,76 +26,86 @@ console.error( "NEED TO DOWNLOAD FOLDSEEK IN THE SERVER. SEE THE SERVER ERROR MESSAGE." ); + errorEncountered = true; } }); -
- - - - - - - - - {#each similarProteins as protein, i} - - - - - - +{#if similarProteins === undefined && !errorEncountered} + +{:else if similarProteins !== undefined} +
+
Name E-Value Prob. Match Alignment Region TMAlign
- - -
- - {protein.evalue.toExponential()} -
-
- - {protein.prob} -
-
- -
-
- -
+ + + + + + - {/each} -
Name E-Value Prob. Match Alignment Region TMAlign
-
+ {#each similarProteins as protein, i} +
+ + +
+ + {protein.evalue.toExponential()} +
+
+ + {protein.prob} +
+
+ +
+
+ +
+
+{:else} + Error in the in the backend. Please contact admins. +{/if} diff --git a/frontend/src/routes/Align.svelte b/frontend/src/routes/Align.svelte new file mode 100644 index 00000000..58eebe7d --- /dev/null +++ b/frontend/src/routes/Align.svelte @@ -0,0 +1,105 @@ + + + + Venome Protein {entryA ? entryA.name : ""} + + +
+
+ {#if entryA && entryB} + +
+ +
+ {:else if !error} + +

+ {:else if error} + +

Error

+ {/if} +
+
+ + diff --git a/frontend/src/routes/Compare.svelte b/frontend/src/routes/Compare.svelte deleted file mode 100644 index 324f5a04..00000000 --- a/frontend/src/routes/Compare.svelte +++ /dev/null @@ -1,73 +0,0 @@ - - - - Venome Protein {entry ? entry.name : ""} - - -
- {#if entry} -
- -

Comparing Proteins

- -
- {proteinA} and {proteinB} -
- -
- {:else if !error} - -

- {:else if error} - -

Error

-

Could not find a protein with the id {urlId}

- {/if} -
- - diff --git a/frontend/src/routes/TMAlignEntry.svelte b/frontend/src/routes/TMAlignEntry.svelte new file mode 100644 index 00000000..477f70b1 --- /dev/null +++ b/frontend/src/routes/TMAlignEntry.svelte @@ -0,0 +1,44 @@ + + + + +
+ {entry.description} +
+
+ Length: {entry.length} residues +
+
+ Mass: {numberWithCommas(entry.mass, 0)} Da +
+
+ + From ac167fc68c0f90a0a8c5bce289e80239cf271dd9 Mon Sep 17 00:00:00 2001 From: xnought Date: Sat, 6 Apr 2024 14:27:01 -0700 Subject: [PATCH 31/67] feat: better upload for thumbnails --- frontend/src/lib/venomeMolstarUtils.ts | 76 ++++++++++++++ .../src/routes/ForceUploadThumbnails.svelte | 99 +++---------------- 2 files changed, 89 insertions(+), 86 deletions(-) create mode 100644 frontend/src/lib/venomeMolstarUtils.ts diff --git a/frontend/src/lib/venomeMolstarUtils.ts b/frontend/src/lib/venomeMolstarUtils.ts new file mode 100644 index 00000000..d3342928 --- /dev/null +++ b/frontend/src/lib/venomeMolstarUtils.ts @@ -0,0 +1,76 @@ +import { BACKEND_URL } from "./backend"; +import { PDBeMolstarPlugin } from "../../venome-molstar/lib"; +import type { InitParams } from "../../venome-molstar/lib/spec"; + +export async function screenshotMolstar(initParams: Partial) { + const { div, molstar } = await renderHeadless(initParams); + const imgData = await getPreview(molstar); + + // cleanup + loseWebGLContext(div.querySelector("canvas")!); + molstar.plugin.dispose(); + div.remove(); + + return imgData; +} + +export function loseWebGLContext(canvas: HTMLCanvasElement) { + const gl = canvas.getContext("webgl"); + if (gl) { + const loseContext = gl.getExtension("WEBGL_lose_context"); + if (loseContext) { + loseContext.loseContext(); + } + } +} + +async function getPreview(m: PDBeMolstarPlugin, checkDelayMs = 25) { + const blankPreview = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAFACAYAAADNkKWqAAAAAXNSR0IArs4c6QAACUJJREFUeF7t1AENADAMAsHNv2iWzMZfHXA03G07jgABAkGBawCDrYtMgMAXMIAegQCBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AgAH0AwQIZAUMYLZ6wQkQMIB+gACBrIABzFYvOAECBtAPECCQFTCA2eoFJ0DAAPoBAgSyAgYwW73gBAgYQD9AgEBWwABmqxecAAED6AcIEMgKGMBs9YITIGAA/QABAlkBA5itXnACBAygHyBAICtgALPVC06AwAM7Tfx9MOLD/gAAAABJRU5ErkJggg=="; + while (true) { + const imgData = m.plugin.helpers.viewportScreenshot + ?.getPreview()! + .canvas.toDataURL()!; + if (imgData !== blankPreview) { + return imgData; + } + await delay(checkDelayMs); + } +} + +async function renderHeadless(initParams: Partial) { + const molstar = new PDBeMolstarPlugin(); + const div = document.createElement("div"); + await molstar.render(div, initParams); + return { div, molstar }; +} + +async function delay(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export const defaultInitParams = (name: string): Partial => ({ + customData: { + url: `${BACKEND_URL}/protein/pdb/${name}`, + format: "pdb", + binary: false, + }, + subscribeEvents: false, + bgColor: { + r: 255, + g: 255, + b: 255, + }, + selectInteraction: false, + alphafoldView: true, + reactive: false, + sequencePanel: false, + hideControls: true, + hideCanvasControls: [ + "animation", + "expand", + "selection", + "controlToggle", + "controlInfo", + ], +}); diff --git a/frontend/src/routes/ForceUploadThumbnails.svelte b/frontend/src/routes/ForceUploadThumbnails.svelte index 399a16f6..de0fbef0 100644 --- a/frontend/src/routes/ForceUploadThumbnails.svelte +++ b/frontend/src/routes/ForceUploadThumbnails.svelte @@ -1,11 +1,10 @@ -
-
Uploading {uploaded} of {proteinsNeedingPng.length}
- preview + {proteinsNeedingPng[uploaded]?.name} +
+
From 31a5d13c110b200458abc9805b1b498c6aba457b Mon Sep 17 00:00:00 2001 From: xnought Date: Sat, 6 Apr 2024 14:33:51 -0700 Subject: [PATCH 32/67] feat: lock png upload with auth --- backend/src/api/protein.py | 11 ++++++----- backend/src/api/users.py | 4 ++-- backend/src/auth.py | 10 +++++----- frontend/src/routes/ForceUploadThumbnails.svelte | 3 ++- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/backend/src/api/protein.py b/backend/src/api/protein.py index 73f7517e..02513c9f 100644 --- a/backend/src/api/protein.py +++ b/backend/src/api/protein.py @@ -8,7 +8,7 @@ from fastapi.exceptions import HTTPException from ..api_types import ProteinEntry, UploadBody, UploadError, EditBody, CamelModel -from ..auth import requiresAuthentication +from ..auth import requires_authentication from ..tmalign import tm_align from io import BytesIO from fastapi import APIRouter @@ -193,7 +193,7 @@ def get_protein_entry(protein_name: str): # TODO: add permissions so only the creator can delete not just anyone @router.delete("/protein/entry/{protein_name:str}", response_model=None) def delete_protein_entry(protein_name: str, req: Request): - requiresAuthentication(req) + requires_authentication(req) # Todo, have a meaningful error if the delete fails with Database() as db: # remove protein @@ -210,7 +210,8 @@ def delete_protein_entry(protein_name: str, req: Request): @router.post("/protein/upload/png", response_model=None) -def upload_protein_png(body: UploadPNGBody): +def upload_protein_png(body: UploadPNGBody, req: Request): + requires_authentication(req) with Database() as db: try: query = """UPDATE proteins SET thumbnail = %s WHERE name = %s""" @@ -222,7 +223,7 @@ def upload_protein_png(body: UploadPNGBody): # None return means success @router.post("/protein/upload", response_model=UploadError | None) def upload_protein_entry(body: UploadBody, req: Request): - requiresAuthentication(req) + requires_authentication(req) body.name = format_protein_name(body.name) # check that the name is not already taken in the DB @@ -290,7 +291,7 @@ def edit_protein_entry(body: EditBody, req: Request): # check that the name is not already taken in the DB # TODO: check if permission so we don't have people overriding other people's names - requiresAuthentication(req) + requires_authentication(req) try: # replace spaces in the name with underscores body.old_name = format_protein_name(body.old_name) diff --git a/backend/src/api/users.py b/backend/src/api/users.py index 5d96d4d8..b3c1ca39 100644 --- a/backend/src/api/users.py +++ b/backend/src/api/users.py @@ -3,7 +3,7 @@ from passlib.hash import bcrypt from ..api_types import LoginBody, LoginResponse from ..db import Database -from ..auth import generateAuthToken +from ..auth import generate_auth_token router = APIRouter() @@ -32,7 +32,7 @@ def login(body: LoginBody): return LoginResponse(token="", error="Invalid Email or Password") # Generates the token and returns - token = generateAuthToken(email, admin) + token = generate_auth_token(email, admin) log.warn("Giving token:", token) return LoginResponse(token=token, error="") diff --git a/backend/src/auth.py b/backend/src/auth.py index 9d491449..eb431a2f 100644 --- a/backend/src/auth.py +++ b/backend/src/auth.py @@ -9,7 +9,7 @@ secret_key = "SuperSecret" -def generateAuthToken(userId, admin): +def generate_auth_token(userId, admin): payload = { "email": userId, "admin": admin, @@ -18,13 +18,13 @@ def generateAuthToken(userId, admin): return jwt.encode(payload, secret_key, algorithm="HS256") -def authenticateToken(token): +def authenticate_token(token): # Return the decoded token if it's valid. try: # Valid token is always is in the form "Bearer [token]", so we need to slice off the "Bearer" portion. sliced_token = token[7:] log.warn(sliced_token) - decoded = jwt.decode(sliced_token, secret_key, algorithms="HS256") + decoded = jwt.decode(sliced_token, secret_key, algorithms=["HS256"]) log.warn("Valid token") log.warn(decoded) return decoded @@ -36,8 +36,8 @@ def authenticateToken(token): # Use this function with a request if you want. -def requiresAuthentication(req: Request): - userInfo = authenticateToken(req.headers["authorization"]) +def requires_authentication(req: Request): + userInfo = authenticate_token(req.headers["authorization"]) if not userInfo or not userInfo.get("admin"): log.error("Unauthorized User") raise HTTPException(status_code=403, detail="Unauthorized") diff --git a/frontend/src/routes/ForceUploadThumbnails.svelte b/frontend/src/routes/ForceUploadThumbnails.svelte index de0fbef0..343da8dd 100644 --- a/frontend/src/routes/ForceUploadThumbnails.svelte +++ b/frontend/src/routes/ForceUploadThumbnails.svelte @@ -1,6 +1,6 @@ {#if showSpinner} - {#if textRight} - {text} - {:else} - {text} - {/if} +
+ {#if textRight} + +

{text}

+ {:else} +

{text}

+ + {/if} +
{/if} diff --git a/frontend/src/routes/Search.svelte b/frontend/src/routes/Search.svelte index 0b921b59..3446e76a 100644 --- a/frontend/src/routes/Search.svelte +++ b/frontend/src/routes/Search.svelte @@ -5,6 +5,7 @@ import ListProteins from "../lib/ListProteins.svelte"; import { Search, Button } from "flowbite-svelte"; import RangerFilter from "../lib/RangerFilter.svelte"; + import DelayedSpinner from "../lib/DelayedSpinner.svelte"; let query = ""; let proteinEntries: ProteinEntry[]; @@ -50,59 +51,63 @@
+