diff --git a/website/src/components/SequenceDetailsPage/SequencesDataTable.astro b/website/src/components/SequenceDetailsPage/SequencesDataTable.astro index d48b46468c..9d3f692d93 100644 --- a/website/src/components/SequenceDetailsPage/SequencesDataTable.astro +++ b/website/src/components/SequenceDetailsPage/SequencesDataTable.astro @@ -2,6 +2,7 @@ import { SequencesContainer } from './SequencesContainer'; import { type TableDataEntry } from './getTableData'; import { getReferenceGenomes, getRuntimeConfig } from '../../config'; +import { routes } from '../../routes.ts'; interface Props { tableData: TableDataEntry[]; @@ -33,6 +34,14 @@ const nucleotideSegmentNames = referenceGenomes.nucleotideSequences.map((s) => s +
+ + Download FASTA + +
{ +export const GET: APIRoute = async ({ params, redirect, request }) => { const accessionVersion = params.accessionVersion!; const organism = params.organism!; - const result = await getSequenceDetailsUnalignedFasta(accessionVersion, organism); + const isDownload = new URL(request.url).searchParams.has('download'); + + const result = await getSequenceDetailsUnalignedFasta(accessionVersion, organism, isDownload); if (!result.isOk()) { return new Response(undefined, { status: 404, @@ -23,10 +25,16 @@ export const GET: APIRoute = async ({ params, redirect }) => { return redirect(result.value.redirectUrl); } + const headers: Record = { + 'Content-Type': 'text/x-fasta', + }; + if (isDownload) { + const filename = `${accessionVersion}.fa`; + headers['Content-Disposition'] = `attachment; filename="${filename}"`; + } + return new Response(result.value.fasta, { - headers: { - 'Content-Type': 'text/x-fasta', - }, + headers, }); }; @@ -48,6 +56,7 @@ type Redirect = { const getSequenceDetailsUnalignedFasta = async ( accessionVersion: string, organism: string, + isDownload: boolean, ): Promise> => { const { accession, version } = parseAccessionVersionFromString(accessionVersion); @@ -57,7 +66,7 @@ const getSequenceDetailsUnalignedFasta = async ( const latestVersionResult = await lapisClient.getLatestAccessionVersion(accession); return latestVersionResult.map((latestVersion) => ({ type: ResultType.REDIRECT, - redirectUrl: routes.sequencesFastaPage(organism, latestVersion), + redirectUrl: routes.sequencesFastaPage(organism, latestVersion, isDownload), })); } diff --git a/website/src/routes.ts b/website/src/routes.ts index f6ff7dcf7b..d35e0aff7d 100644 --- a/website/src/routes.ts +++ b/website/src/routes.ts @@ -24,8 +24,13 @@ export const routes = { `/${organism}/seq/${getAccessionVersionString(accessionVersion)}`, sequencesVersionsPage: (organism: string, accessionVersion: AccessionVersion | string) => `/${organism}/seq/${getAccessionVersionString(accessionVersion)}/versions`, - sequencesFastaPage: (organism: string, accessionVersion: AccessionVersion | string) => - `${routes.sequencesDetailsPage(organism, accessionVersion)}.fa`, + sequencesFastaPage: (organism: string, accessionVersion: AccessionVersion | string, download = false) => { + let url = `${routes.sequencesDetailsPage(organism, accessionVersion)}.fa`; + if (download) { + url += '?download'; + } + return url; + }, submitPage: (organism: string) => withOrganism(organism, '/submit'), revisePage: (organism: string) => withOrganism(organism, '/revise'), editPage: (organism: string, accessionVersion: AccessionVersion) => diff --git a/website/tests/pages/sequences/accession.fa.spec.ts b/website/tests/pages/sequences/accession.fa.spec.ts index 525f7bc04b..9f35f49c0f 100644 --- a/website/tests/pages/sequences/accession.fa.spec.ts +++ b/website/tests/pages/sequences/accession.fa.spec.ts @@ -4,7 +4,18 @@ import { baseUrl, dummyOrganism, expect, test, testSequenceEntry } from '../../e test.describe('The sequence.fa page', () => { test('can load and show fasta file', async () => { const url = `${baseUrl}${routes.sequencesFastaPage(dummyOrganism.key, testSequenceEntry)}`; - const content = await (await fetch(url)).text(); + const response = await fetch(url); + const content = await response.text(); expect(content).toBe(`>${testSequenceEntry.name}\n${testSequenceEntry.unaligned}\n`); }); + + test('can download fasta file', async () => { + const downloadUrl = `${baseUrl}${routes.sequencesFastaPage(dummyOrganism.key, testSequenceEntry, true)}`; + const response = await fetch(downloadUrl); + const contentDisposition = response.headers.get('Content-Disposition'); + + expect(contentDisposition).not.toBeNull(); + expect(contentDisposition).toContain('attachment'); + expect(contentDisposition).toContain(testSequenceEntry.name); + }); });