diff --git a/examples/todo application (PostgreSQL)/csv_attachment.sql b/examples/todo application (PostgreSQL)/csv_attachment.sql new file mode 100644 index 00000000..8c37a9b8 --- /dev/null +++ b/examples/todo application (PostgreSQL)/csv_attachment.sql @@ -0,0 +1,6 @@ +select + 'csv_attachment' as component, + ';' as separator, + 'todo.csv' as filename; + +select * from todos; \ No newline at end of file diff --git a/examples/todo application (PostgreSQL)/index.sql b/examples/todo application (PostgreSQL)/index.sql index 699d08db..2217c300 100644 --- a/examples/todo application (PostgreSQL)/index.sql +++ b/examples/todo application (PostgreSQL)/index.sql @@ -17,4 +17,8 @@ select 'todo_form.sql' as link, 'green' as color, 'Add new todo' as title, - 'circle-plus' as icon; \ No newline at end of file + 'circle-plus' as icon; +select + '/csv_attachment.sql' as link, + 'Download' as title, + 'download' as icon; \ No newline at end of file diff --git a/sqlpage/templates/csv_attachment.handlebars b/sqlpage/templates/csv_attachment.handlebars new file mode 100644 index 00000000..774dc6cc --- /dev/null +++ b/sqlpage/templates/csv_attachment.handlebars @@ -0,0 +1,6 @@ +{{#each_row}} +{{#if (eq @row_index 0)}} +{{#each this}}{{@key}}{{#unless @last}}{{default ../../separator ","}}{{/unless}}{{/each}}{{../linebreak}} +{{/if}} +{{#each this}}{{this}}{{#unless @last}}{{default ../../separator ","}}{{/unless}}{{/each}}{{../linebreak}} +{{/each_row}} diff --git a/src/render.rs b/src/render.rs index 403124aa..471459d5 100644 --- a/src/render.rs +++ b/src/render.rs @@ -60,6 +60,7 @@ impl<'a, W: std::io::Write> HeaderContext { Some("http_header") => self.add_http_header(&data).map(PageContext::Header), Some("redirect") => self.redirect(&data).map(PageContext::Close), Some("json") => self.json(&data).map(PageContext::Close), + Some("csv_attachment") => self.csv_attachment(data).await, Some("cookie") => self.add_cookie(&data).map(PageContext::Header), Some("authentication") => self.authentication(data).await, _ => self.start_body(data).await, @@ -201,6 +202,25 @@ impl<'a, W: std::io::Write> HeaderContext { Ok(self.response.body(json_response)) } + async fn csv_attachment(mut self, data: JsonValue) -> anyhow::Result> { + let filename = data + .get("filename") + .and_then(JsonValue::as_str) + .unwrap_or("output.csv"); + + self.response + .insert_header((header::CONTENT_TYPE, "text/csv")) + .insert_header(( + header::CONTENT_DISPOSITION, + format!("attachment; filename=\"{}\"", filename), + )); + + self.request_context.is_embedded = true; + let page_content = self.start_body(data).await?; + + Ok(page_content) + } + async fn authentication(mut self, mut data: JsonValue) -> anyhow::Result> { let password_hash = take_object_str(&mut data, "password_hash"); let password = take_object_str(&mut data, "password");