Skip to content

Commit f8b13dd

Browse files
committed
add support for cookies
1 parent de948fb commit f8b13dd

File tree

8 files changed

+170
-2
lines changed

8 files changed

+170
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sqlpage"
3-
version = "0.6.12"
3+
version = "0.7.0"
44
edition = "2021"
55
description = "A SQL-only web application framework. Takes .sql files and formats the query result using pre-made configurable professional-looking components."
66
keywords = ["web", "sql", "framework"]
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
-- Insert the http_header component into the component table
2+
INSERT INTO component (name, description, icon)
3+
VALUES (
4+
'cookie',
5+
'Sets a cookie in the client browser, used for session management and storing user-related information.
6+
7+
This component creates a single cookie. Since cookies need to be set before the response body is sent to the client,
8+
this component should be placed at the top of the page, before any other components that generate output.
9+
10+
After being set, a cookie can be accessed anywhere in your SQL code using the `sqlpage.cookie(''cookie_name'')` pseudo-function.',
11+
'cookie'
12+
);
13+
-- Insert the parameters for the http_header component into the parameter table
14+
INSERT INTO parameter (
15+
component,
16+
name,
17+
description,
18+
type,
19+
top_level,
20+
optional
21+
)
22+
VALUES (
23+
'cookie',
24+
'name',
25+
'The name of the cookie to set.',
26+
'TEXT',
27+
TRUE,
28+
FALSE
29+
),
30+
(
31+
'cookie',
32+
'value',
33+
'The value of the cookie to set.',
34+
'TEXT',
35+
TRUE,
36+
TRUE
37+
),
38+
(
39+
'cookie',
40+
'path',
41+
'The path for which the cookie will be sent. If not specified, the cookie will be sent for all paths.',
42+
'TEXT',
43+
TRUE,
44+
TRUE
45+
),
46+
(
47+
'cookie',
48+
'domain',
49+
'The domain for which the cookie will be sent. If not specified, the cookie will be sent for all domains.',
50+
'TEXT',
51+
TRUE,
52+
TRUE
53+
),
54+
(
55+
'cookie',
56+
'secure',
57+
'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.',
58+
'BOOLEAN',
59+
TRUE,
60+
TRUE
61+
),
62+
(
63+
'cookie',
64+
'http_only',
65+
'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.',
66+
'BOOLEAN',
67+
TRUE,
68+
TRUE
69+
),
70+
(
71+
'cookie',
72+
'remove',
73+
'Set to TRUE to remove the cookie from the client browser. When specified, other parameters are ignored.',
74+
'BOOLEAN',
75+
TRUE,
76+
TRUE
77+
)
78+
;
79+
-- Insert an example usage of the http_header component into the example table
80+
INSERT INTO example (component, description, properties)
81+
VALUES (
82+
'cookie',
83+
'Create a cookie named `username` with the value `John Doe`...
84+
85+
```sql
86+
SELECT ''cookie'' as component,
87+
''username'' as name,
88+
''John Doe'' as value;
89+
```
90+
91+
and then display the value of the cookie:
92+
93+
```sql
94+
SELECT ''text'' as component,
95+
''Your name is '' || COALESCE(sqlpage.cookie(''username''), ''not known to us'');
96+
```
97+
',
98+
JSON('[]')
99+
);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# SQLPage application with custom login in Postgres
2+
3+
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.
4+
5+
It lets an user log in and out, and it shows a list of the users that have logged in.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- Sets the username cookie to the value of the username parameter
2+
SELECT 'cookie' as component,
3+
'username' as name,
4+
$username as value
5+
WHERE $username IS NOT NULL;
6+
7+
SELECT 'form' as component;
8+
SELECT 'username' as name,
9+
'User Name' as label,
10+
COALESCE($username, sqlpage.cookie('username')) as value,
11+
'try leaving this page and coming back, the value should be saved in a cookie' as description;
12+
13+
select 'text' as component;
14+
select 'log out' as contents, 'logout.sql' as link;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- Sets the username cookie to the value of the username parameter
2+
SELECT 'cookie' as component,
3+
'username' as name,
4+
TRUE as remove;
5+
6+
SELECT 'http_header' as component, 'index.sql' as Location;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"database_url": "sqlite://:memory:"
3+
}

src/render.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ impl<W: std::io::Write> HeaderContext<W> {
4242
match get_object_str(&data, "component") {
4343
Some("status_code") => self.status_code(&data).map(PageContext::Header),
4444
Some("http_header") => self.add_http_header(&data).map(PageContext::Header),
45+
Some("cookie") => self.add_cookie(&data).map(PageContext::Header),
4546
_ => self.start_body(data).await,
4647
}
4748
}
@@ -78,6 +79,46 @@ impl<W: std::io::Write> HeaderContext<W> {
7879
Ok(self)
7980
}
8081

82+
fn add_cookie(mut self, data: &JsonValue) -> anyhow::Result<Self> {
83+
let obj = data.as_object().with_context(|| "expected object")?;
84+
let name = obj
85+
.get("name")
86+
.and_then(JsonValue::as_str)
87+
.with_context(|| "cookie name must be a string")?;
88+
let mut cookie = actix_web::cookie::Cookie::named(name);
89+
90+
let remove = obj.get("remove");
91+
if remove == Some(&json!(true)) || remove == Some(&json!(1)) {
92+
self.response.cookie(cookie);
93+
return Ok(self);
94+
}
95+
96+
let value = obj
97+
.get("value")
98+
.and_then(JsonValue::as_str)
99+
.with_context(|| "cookie value must be a string")?;
100+
cookie.set_value(value);
101+
let http_only = obj.get("http_only");
102+
cookie.set_http_only(http_only != Some(&json!(false)) && http_only != Some(&json!(0)));
103+
let secure = obj.get("secure");
104+
cookie.set_secure(secure != Some(&json!(false)) && secure != Some(&json!(0)));
105+
let path = obj.get("path").and_then(JsonValue::as_str);
106+
if let Some(path) = path {
107+
cookie.set_path(path);
108+
}
109+
let domain = obj.get("domain").and_then(JsonValue::as_str);
110+
if let Some(domain) = domain {
111+
cookie.set_domain(domain);
112+
}
113+
let expires = obj.get("expires").and_then(JsonValue::as_i64);
114+
if let Some(expires) = expires {
115+
cookie.set_expires(actix_web::cookie::Expiration::DateTime(
116+
actix_web::cookie::time::OffsetDateTime::from_unix_timestamp(expires)?,
117+
));
118+
}
119+
self.response.cookie(cookie);
120+
Ok(self)
121+
}
81122
async fn start_body(self, data: JsonValue) -> anyhow::Result<PageContext<W>> {
82123
let renderer = RenderContext::new(self.app_state, self.writer, data).await?;
83124
let http_response = self.response;

0 commit comments

Comments
 (0)