Skip to content

Commit

Permalink
add support for cookies
Browse files Browse the repository at this point in the history
  • Loading branch information
lovasoa committed Jun 21, 2023
1 parent de948fb commit f8b13dd
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "sqlpage"
version = "0.6.12"
version = "0.7.0"
edition = "2021"
description = "A SQL-only web application framework. Takes .sql files and formats the query result using pre-made configurable professional-looking components."
keywords = ["web", "sql", "framework"]
Expand Down
99 changes: 99 additions & 0 deletions examples/official-site/sqlpage/migrations/05_cookie.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
-- Insert the http_header component into the component table
INSERT INTO component (name, description, icon)
VALUES (
'cookie',
'Sets a cookie in the client browser, used for session management and storing user-related information.
This component creates a single cookie. Since cookies need to be set before the response body is sent to the client,
this component should be placed at the top of the page, before any other components that generate output.
After being set, a cookie can be accessed anywhere in your SQL code using the `sqlpage.cookie(''cookie_name'')` pseudo-function.',
'cookie'
);
-- Insert the parameters for the http_header component into the parameter table
INSERT INTO parameter (
component,
name,
description,
type,
top_level,
optional
)
VALUES (
'cookie',
'name',
'The name of the cookie to set.',
'TEXT',
TRUE,
FALSE
),
(
'cookie',
'value',
'The value of the cookie to set.',
'TEXT',
TRUE,
TRUE
),
(
'cookie',
'path',
'The path for which the cookie will be sent. If not specified, the cookie will be sent for all paths.',
'TEXT',
TRUE,
TRUE
),
(
'cookie',
'domain',
'The domain for which the cookie will be sent. If not specified, the cookie will be sent for all domains.',
'TEXT',
TRUE,
TRUE
),
(
'cookie',
'secure',
'Whether the cookie should only be sent over a secure (HTTPS) connection. If not specified, the cookie will be sent over both secure and non-secure connections.',
'BOOLEAN',
TRUE,
TRUE
),
(
'cookie',
'http_only',
'Whether the cookie should only be accessible via HTTP and not via client-side scripts. If not specified, the cookie will be accessible via both HTTP and client-side scripts.',
'BOOLEAN',
TRUE,
TRUE
),
(
'cookie',
'remove',
'Set to TRUE to remove the cookie from the client browser. When specified, other parameters are ignored.',
'BOOLEAN',
TRUE,
TRUE
)
;
-- Insert an example usage of the http_header component into the example table
INSERT INTO example (component, description, properties)
VALUES (
'cookie',
'Create a cookie named `username` with the value `John Doe`...
```sql
SELECT ''cookie'' as component,
''username'' as name,
''John Doe'' as value;
```
and then display the value of the cookie:
```sql
SELECT ''text'' as component,
''Your name is '' || COALESCE(sqlpage.cookie(''username''), ''not known to us'');
```
',
JSON('[]')
);
5 changes: 5 additions & 0 deletions examples/read-and-set-http-cookies/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SQLPage application with custom login in Postgres

This is a very simple example of a website that uses the SQLPage web application framework. It uses a Postgres database for storing the data.

It lets an user log in and out, and it shows a list of the users that have logged in.
14 changes: 14 additions & 0 deletions examples/read-and-set-http-cookies/index.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- Sets the username cookie to the value of the username parameter
SELECT 'cookie' as component,
'username' as name,
$username as value
WHERE $username IS NOT NULL;

SELECT 'form' as component;
SELECT 'username' as name,
'User Name' as label,
COALESCE($username, sqlpage.cookie('username')) as value,
'try leaving this page and coming back, the value should be saved in a cookie' as description;

select 'text' as component;
select 'log out' as contents, 'logout.sql' as link;
6 changes: 6 additions & 0 deletions examples/read-and-set-http-cookies/logout.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Sets the username cookie to the value of the username parameter
SELECT 'cookie' as component,
'username' as name,
TRUE as remove;

SELECT 'http_header' as component, 'index.sql' as Location;
3 changes: 3 additions & 0 deletions examples/read-and-set-http-cookies/sqlpage/sqlpage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"database_url": "sqlite://:memory:"
}
41 changes: 41 additions & 0 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ impl<W: std::io::Write> HeaderContext<W> {
match get_object_str(&data, "component") {
Some("status_code") => self.status_code(&data).map(PageContext::Header),
Some("http_header") => self.add_http_header(&data).map(PageContext::Header),
Some("cookie") => self.add_cookie(&data).map(PageContext::Header),
_ => self.start_body(data).await,
}
}
Expand Down Expand Up @@ -78,6 +79,46 @@ impl<W: std::io::Write> HeaderContext<W> {
Ok(self)
}

fn add_cookie(mut self, data: &JsonValue) -> anyhow::Result<Self> {
let obj = data.as_object().with_context(|| "expected object")?;
let name = obj
.get("name")
.and_then(JsonValue::as_str)
.with_context(|| "cookie name must be a string")?;
let mut cookie = actix_web::cookie::Cookie::named(name);

let remove = obj.get("remove");
if remove == Some(&json!(true)) || remove == Some(&json!(1)) {
self.response.cookie(cookie);
return Ok(self);
}

let value = obj
.get("value")
.and_then(JsonValue::as_str)
.with_context(|| "cookie value must be a string")?;
cookie.set_value(value);
let http_only = obj.get("http_only");
cookie.set_http_only(http_only != Some(&json!(false)) && http_only != Some(&json!(0)));
let secure = obj.get("secure");
cookie.set_secure(secure != Some(&json!(false)) && secure != Some(&json!(0)));
let path = obj.get("path").and_then(JsonValue::as_str);
if let Some(path) = path {
cookie.set_path(path);
}
let domain = obj.get("domain").and_then(JsonValue::as_str);
if let Some(domain) = domain {
cookie.set_domain(domain);
}
let expires = obj.get("expires").and_then(JsonValue::as_i64);
if let Some(expires) = expires {
cookie.set_expires(actix_web::cookie::Expiration::DateTime(
actix_web::cookie::time::OffsetDateTime::from_unix_timestamp(expires)?,
));
}
self.response.cookie(cookie);
Ok(self)
}
async fn start_body(self, data: JsonValue) -> anyhow::Result<PageContext<W>> {
let renderer = RenderContext::new(self.app_state, self.writer, data).await?;
let http_response = self.response;
Expand Down

0 comments on commit f8b13dd

Please sign in to comment.