Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add references in the article #89

Merged
merged 19 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions backend/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ CREATE TABLE proteins (
name text NOT NULL UNIQUE PRIMARY KEY, -- user specified name of the protein (TODO: consider having a string limit)
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)
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
);

/*
Expand All @@ -45,24 +46,27 @@ CREATE TABLE species (
/*
* Inserts example proteins into proteins table
*/
INSERT INTO proteins (name, length, mass, content) VALUES (
INSERT INTO proteins (name, length, mass, content, refs) VALUES (
'Gh_comp271_c0_seq1',
0,
0.0,
null,
null
);

INSERT INTO proteins (name, length, mass, content) VALUES (
INSERT INTO proteins (name, length, mass, content, refs) VALUES (
'Lb17_comp535_c2_seq1',
0,
0.0,
null,
null
);

INSERT INTO proteins (name, length, mass, content) VALUES (
INSERT INTO proteins (name, length, mass, content, refs) VALUES (
'Lh14_comp2336_c0_seq1',
0,
0.0,
null,
null
);

Expand Down
5 changes: 4 additions & 1 deletion backend/src/api_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class ProteinEntry(CamelModel):
length: int
mass: float
content: str | None = None
refs: str | None = None


class AllEntries(CamelModel):
Expand All @@ -37,7 +38,8 @@ class AllEntries(CamelModel):
class UploadBody(CamelModel):
name: str
content: str # markdown content from user
pdb_file_base64: str
refs: str # references used in content (bibtex form)
pdb_file_str: str


class UploadError(str, enum.Enum):
Expand All @@ -55,3 +57,4 @@ class EditBody(CamelModel):
old_name: str # so we can identify the exact row we need to change
new_name: str
new_content: str | None = None
new_refs: str | None = None
40 changes: 28 additions & 12 deletions backend/src/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def get_protein_entry(protein_name: str):
with Database() as db:
try:
entry_sql = db.execute_return(
"""SELECT name, length, mass, content FROM proteins
"""SELECT name, length, mass, content, refs FROM proteins
WHERE name = %s""",
[protein_name],
)
Expand All @@ -53,13 +53,16 @@ def get_protein_entry(protein_name: str):
if entry_sql is not None and len(entry_sql) != 0:
# return the only entry
only_returned_entry = entry_sql[0]
name, length, mass, content = only_returned_entry
# if bytes are present, decode them into a string
name, length, mass, content, refs = 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)

return ProteinEntry(
name=name, length=length, mass=mass, content=content
name=name, length=length, mass=mass, content=content, refs=refs
)

except Exception as e:
Expand Down Expand Up @@ -95,7 +98,7 @@ def upload_protein_entry(body: UploadBody):
# if name is unique, save the pdb file and add the entry to the database
try:
# TODO: consider somehow sending the file as a stream instead of a b64 string or send as regular string
pdb = parse_protein_pdb(body.name, body.pdb_file_base64, encoding="b64")
pdb = parse_protein_pdb(body.name, body.pdb_file_str)
except Exception:
return UploadError.PARSE_ERROR

Expand All @@ -107,12 +110,13 @@ def upload_protein_entry(body: UploadBody):
# save to db
with Database() as db:
db.execute(
"""INSERT INTO proteins (name, length, mass, content) VALUES (%s, %s, %s, %s);""",
"""INSERT INTO proteins (name, length, mass, content, refs) VALUES (%s, %s, %s, %s, %s);""",
[
pdb.name,
pdb.num_amino_acids,
pdb.mass_daltons,
str_to_bytea(body.content),
str_to_bytea(body.refs),
],
)
except Exception:
Expand All @@ -133,21 +137,33 @@ def edit_protein_entry(body: EditBody):
os.rename(pdb_file_name(body.old_name), pdb_file_name(body.new_name))

with Database() as db:
# if we have content/markdown, then update it, otherwise just update the name
if body.new_content is not None:
if body.new_name != body.old_name:
db.execute(
"""UPDATE proteins SET name = %s, content = %s WHERE name = %s""",
"""UPDATE proteins SET name = %s WHERE name = %s""",
[
body.new_name,
body.old_name,
],
)

if body.new_content is not None:
db.execute(
"""UPDATE proteins SET content = %s WHERE name = %s""",
[
str_to_bytea(body.new_content),
body.old_name,
],
)
else:

if body.new_refs is not None:
db.execute(
"""UPDATE proteins SET name = %s WHERE name = %s""",
[body.new_name, body.old_name],
"""UPDATE proteins SET refs = %s WHERE name = %s""",
[
str_to_bytea(body.new_refs),
body.old_name,
],
)

except Exception:
return UploadError.WRITE_ERROR

Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
},
"type": "module",
"dependencies": {
"bibtex": "^0.9.0",
"marked": "^10.0.0"
}
}
60 changes: 60 additions & 0 deletions frontend/src/lib/ArticleEditor.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<script lang="ts">
import {
Card,
Tabs,
TabItem,
Heading,
Label,
Textarea,
} from "flowbite-svelte";
import Markdown from "$lib/Markdown.svelte";
import References from "$lib/References.svelte";
export let refs = "";
export let content = "";
</script>

<Card
class="max-w-full"
style="height: 600px; overflow-y: scroll; padding: 0; padding-top: 4px; padding-left: 4px;"
>
<Tabs contentClass="bg-none p-5" style="underline">
<TabItem title="article content" open>
<div>
<Label for="content" class="block mb-2"
>Protein Article (Markdown)</Label
>
<Textarea
id="content"
placeholder="Enter markdown..."
rows={12}
bind:value={content}
/>
</div>

<div class="mt-3">
<Label for="refs" class="block mb-2">References (BibTeX)</Label>
<Textarea
id="refs"
placeholder="Enter bibtex with atleast an id, title, and author (optionally url and year)"
rows={4}
bind:value={refs}
/>
</div>
</TabItem>
<TabItem title="preview">
{#if content.length > 0 || refs.length > 0}
<Card class="max-w-full">
<Heading tag="h4">Article</Heading>
<Markdown text={content} />
</Card>

<Card class="max-w-full mt-5">
<Heading tag="h4">References</Heading>
<References bibtex={String.raw`${refs}`} />
</Card>
{:else}
No content to render/preview
{/if}
</TabItem>
</Tabs>
</Card>
16 changes: 15 additions & 1 deletion frontend/src/lib/Markdown.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,21 @@

export let text = ``;
// they recommend also sanitizing input text https://github.com/cure53/DOMPurify
$: mdToHTML = marked(text);

$: mdToHTML = marked(replaceCite(text));

/**
* @todo this is a hacky way to do this, but it works for now
* Instead use the builtin extensions https://marked.js.org/using_pro#extensions
*/
function replaceCite(str: string) {
// replace \cite{} with <a href="#ref-1">[1]</a>
const newStr = str.replaceAll(/\\cite{(.+?)}/g, (match, p1) => {
console.log(match, p1);
return `[<a href="#${p1}">${p1}</a>]`;
});
return newStr;
}
</script>

{@html mdToHTML}
68 changes: 68 additions & 0 deletions frontend/src/lib/References.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script lang="ts">
/* Put stuff here */
import { parseBibFile, type BibEntry, BibFilePresenter } from "bibtex";

export let bibtex = String.raw``;
let bib: BibFilePresenter;
$: {
try {
bib = parseBibFile(bibtex);
} catch (e) {
console.log("error in syntax");
}
}

/**
* @returns string of authors
*/
function parseAuthors(entry: BibEntry) {
const authors = entry.getFieldAsString("author");
// if a number or not found, error in parsing, so do nothing
if (!authors || typeof authors === "number")
return "[error in parsing authors]";

const parsed = authors.split(" and ").map((author) =>
author
.split(",")
.map((d) => d.trim())
.reverse()
.join(" ")
);
return new Intl.ListFormat("en").format(parsed);
}
</script>

{#if bib}
{#each bib.entries_raw as entry, i}
<div class={i > 0 ? "mt-5" : ""} id={entry._id}>
<div class="bg-gray-50 text-gray-400">
[<span style="font-size: 15px;">{entry._id}</span>]
</div>
<div class="border-l-2 border-gray-400 pl-2">
<div style="font-size: 17px;">
{#if entry.getFieldAsString("url")}
<a href={`${entry.getFieldAsString("url")}`}>
<b>
{entry.getFieldAsString("title")}
</b>
</a>
{:else}
<b>
{entry.getFieldAsString("title")}
</b>
{/if}
</div>

<p>{parseAuthors(entry)}</p>
{#if entry.getFieldAsString("journal")}
<i>
{entry.getFieldAsString("journal")}
{entry.getFieldAsString("year")}
</i>
{/if}
</div>
</div>
{/each}
{:else}
<span class="text-red-700 font-bold"> BibTeX Syntax Error </span>
{/if}
11 changes: 11 additions & 0 deletions frontend/src/lib/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,14 @@ export function formatProteinName(name: string) {
export function humanReadableProteinName(name: string) {
return name.replaceAll("_", " ");
}

export function fileToString(f: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsText(f);
reader.onload = () => {
resolve(reader.result as string);
};
reader.onerror = reject;
});
}
1 change: 1 addition & 0 deletions frontend/src/openapi/models/EditBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export type EditBody = {
oldName: string;
newName: string;
newContent?: (string | null);
newRefs?: (string | null);
};

1 change: 1 addition & 0 deletions frontend/src/openapi/models/ProteinEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export type ProteinEntry = {
length: number;
mass: number;
content?: (string | null);
refs?: (string | null);
};

3 changes: 2 additions & 1 deletion frontend/src/openapi/models/UploadBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
export type UploadBody = {
name: string;
content: string;
pdbFileBase64: string;
refs: string;
pdbFileStr: string;
};

Loading