Skip to content

Commit f865b1f

Browse files
olivierauverlotOlivier Auverlotlovasoa
committed
New RSS component (#252)
* Produces a data flow in the RSS format * add documentation and rss reference * more complete rss component, with support for podcasts * podcast changes * documentation * format dates as rfc2822 * extract template helpers * extract more helpers * remove unnecassary error handling * fmt * two-param helpers * rfc2822_date_helper * test rfc date formatter * accept dates with or without time * remove unneeded line breaks --------- Co-authored-by: Olivier Auverlot <[email protected]> Co-authored-by: lovasoa <[email protected]>
1 parent 74527bc commit f865b1f

File tree

10 files changed

+730
-233
lines changed

10 files changed

+730
-233
lines changed

examples/official-site/blog.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ select 'shell' as component,
1010
'Poppins' as font,
1111
'https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-core.min.js' as javascript,
1212
'https://cdn.jsdelivr.net/npm/prismjs@1/plugins/autoloader/prism-autoloader.min.js' as javascript,
13+
'./rss.sql' as rss,
1314
'/prism-tabler-theme.css' as css;
1415

1516
SELECT 'text' AS component,

examples/official-site/index.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ select 'shell' as component,
99
'blog' as menu_item,
1010
'documentation' as menu_item,
1111
19 as font_size,
12-
'Poppins' as font;
12+
'Poppins' as font,
13+
'./rss.sql' as rss;
1314

1415
SELECT 'hero' as component,
1516
'SQLPage' as title,

examples/official-site/rss.sql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
select 'http_header' as component,
2+
'application/rss+xml' as "Content-Type";
3+
select 'shell-empty' as component;
4+
select 'rss' as component,
5+
'SQLPage blog' as title,
6+
'https://sql.ophir.dev/blog.sql' as link,
7+
'latest news about SQLpage' as description,
8+
'en' as language,
9+
'https://sql.ophir.dev/rss.sql' as self_link,
10+
'Technology' as category,
11+
'2de3f968-9928-5ec6-9653-6fc6fe382cfd' as guid;
12+
SELECT title,
13+
description,
14+
CASE
15+
WHEN external_url IS NOT NULL THEN external_url
16+
ELSE 'https://sql.ophir.dev/blog.sql?post=' || title
17+
END AS link,
18+
created_at AS date,
19+
false AS explicit
20+
FROM blog_posts
21+
ORDER BY created_at DESC;

examples/official-site/sqlpage/migrations/01_documentation.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,7 @@ INSERT INTO parameter(component, name, description, type, top_level, optional) S
723723
('link', 'The target of the link in the top navigation bar.', 'URL', TRUE, TRUE),
724724
('css', 'The URL of a CSS file to load and apply to the page.', 'URL', TRUE, TRUE),
725725
('javascript', 'The URL of a Javascript file to load and execute on the page.', 'URL', TRUE, TRUE),
726+
('rss', 'The URL of an RSS feed to display in the top navigation bar. You can use the rss component to generate the field.', 'URL', TRUE, TRUE),
726727
('image', 'The URL of an image to display next to the page title.', 'URL', TRUE, TRUE),
727728
('icon', 'Name of an icon (from tabler-icons.io) to display next to the title in the navigation bar.', 'ICON', TRUE, TRUE),
728729
('menu_item', 'Adds a menu item in the navigation bar at the top of the page. The menu item will have the specified name, and will link to as .sql file of the same name. A dropdown can be generated by passing a json object with a `title` and `submenu` properties.', 'TEXT', TRUE, TRUE),
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
-- Documentation for the RSS component
2+
INSERT INTO component (name, description, icon, introduced_in_version) VALUES (
3+
'rss',
4+
'Produces a data flow in the RSS format.
5+
Can be used to generate a podcast feed.
6+
To use this component, you must first return an HTTP header with the "application/rss+xml" content type (see http_header component). Next, you must use the shell-empty component to avoid that SQLPage generates HTML code.',
7+
'rss',
8+
'0.20.0'
9+
);
10+
11+
INSERT INTO parameter (component,name,description,type,top_level,optional) VALUES (
12+
'rss',
13+
'title',
14+
'Defines the title of the channel.',
15+
'TEXT',
16+
TRUE,
17+
FALSE
18+
),(
19+
'rss',
20+
'link',
21+
'Defines the hyperlink to the channel.',
22+
'URL',
23+
TRUE,
24+
FALSE
25+
),(
26+
'rss',
27+
'description',
28+
'Describes the channel.',
29+
'TEXT',
30+
TRUE,
31+
FALSE
32+
),(
33+
'rss',
34+
'language',
35+
'Defines the language of the channel, specified in the ISO 639 format. For example, "en" for English, "fr" for French.',
36+
'TEXT',
37+
TRUE,
38+
TRUE
39+
),(
40+
'rss',
41+
'category',
42+
'Defines the category of the channel. The value should be a string representing the category (e.g., "News", "Technology", etc.).',
43+
'TEXT',
44+
TRUE,
45+
TRUE
46+
),(
47+
'rss',
48+
'explicit',
49+
'Indicates whether the channel contains explicit content. The value can be either TRUE or FALSE.',
50+
'BOOLEAN',
51+
TRUE,
52+
TRUE
53+
),(
54+
'rss',
55+
'image_url',
56+
'Provides a URL linking to the artwork for the channel.',
57+
'URL',
58+
TRUE,
59+
TRUE
60+
),(
61+
'rss',
62+
'author',
63+
'Defines the group, person, or people responsible for creating the channel.',
64+
'TEXT',
65+
TRUE,
66+
TRUE
67+
),(
68+
'rss',
69+
'copyright',
70+
'Provides the copyright details for the channel.',
71+
'TEXT',
72+
TRUE,
73+
TRUE
74+
),(
75+
'rss',
76+
'self_link',
77+
'URL of the RSS feed.',
78+
'URL',
79+
TRUE,
80+
TRUE
81+
),(
82+
'rss',
83+
'funding_url',
84+
'Specifies the donation/funding links for the channel. The content of the tag is the recommended string to be used with the link.',
85+
'URL',
86+
TRUE,
87+
TRUE
88+
),(
89+
'rss',
90+
'type',
91+
'Specifies the channel as either episodic or serial. The value can be either "episodic" or "serial".',
92+
'TEXT',
93+
TRUE,
94+
TRUE
95+
),(
96+
'rss',
97+
'complete',
98+
'Specifies that a channel is complete and will not post any more items in the future.',
99+
'BOOLEAN',
100+
TRUE,
101+
TRUE
102+
),(
103+
'rss',
104+
'locked',
105+
'Tells podcast hosting platforms whether they are allowed to import this feed.',
106+
'BOOLEAN',
107+
TRUE,
108+
TRUE
109+
),(
110+
'rss',
111+
'guid',
112+
'The globally unique identifier (GUID) for a channel. The value is a UUIDv5.',
113+
'TEXT',
114+
TRUE,
115+
TRUE
116+
),(
117+
'rss',
118+
'title',
119+
'Defines the title of the feed item (episode name, blog post title, etc.).',
120+
'TEXT',
121+
FALSE,
122+
FALSE
123+
),(
124+
'rss',
125+
'link',
126+
'Defines the hyperlink to the item (blog post URL, etc.).',
127+
'URL',
128+
FALSE,
129+
FALSE
130+
),(
131+
'rss',
132+
'description',
133+
'Describes the item',
134+
'TEXT',
135+
FALSE,
136+
FALSE
137+
),(
138+
'rss',
139+
'date',
140+
'Indicates when the item was published (RFC-822 date-time).',
141+
'TEXT',
142+
FALSE,
143+
TRUE
144+
),(
145+
'rss',
146+
'enclosure_url',
147+
'For podcast episodes, provides a URL linking to the audio/video episode content, in mp3, m4a, m4v, or mp4 format.',
148+
'URL',
149+
FALSE,
150+
TRUE
151+
),(
152+
'rss',
153+
'enclosure_length',
154+
'The length in bytes of the audio/video episode content.',
155+
'INTEGER',
156+
FALSE,
157+
TRUE
158+
),(
159+
'rss',
160+
'enclosure_type',
161+
'The MIME media type of the audio/video episode content (e.g., "audio/mpeg", "audio/m4a", "video/m4v", "video/mp4").',
162+
'TEXT',
163+
FALSE,
164+
TRUE
165+
),(
166+
'rss',
167+
'guid',
168+
'The globally unique identifier (GUID) for an item.',
169+
'TEXT',
170+
FALSE,
171+
TRUE
172+
),(
173+
'rss',
174+
'episode',
175+
'The chronological number that is associated with an item.',
176+
'INTEGER',
177+
FALSE,
178+
TRUE
179+
),(
180+
'rss',
181+
'season',
182+
'The chronological number associated with an item''s season.',
183+
'INTEGER',
184+
FALSE,
185+
TRUE
186+
),(
187+
'rss',
188+
'episode_type',
189+
'Defines the type of content for a specific item. The value can be either "full", "trailer", or "bonus".',
190+
'TEXT',
191+
FALSE,
192+
TRUE
193+
),(
194+
'rss',
195+
'block',
196+
'Prevents a specific item from appearing in podcast listening applications.',
197+
'BOOLEAN',
198+
FALSE,
199+
TRUE
200+
),(
201+
'rss',
202+
'explicit',
203+
'Indicates whether the item contains explicit content. The value can be either TRUE or FALSE.',
204+
'BOOLEAN',
205+
FALSE,
206+
TRUE
207+
),(
208+
'rss',
209+
'image_url',
210+
'Provides a URL linking to the artwork for the item.',
211+
'URL',
212+
FALSE,
213+
TRUE
214+
),(
215+
'rss',
216+
'duration',
217+
'The duration of an item in seconds.',
218+
'INTEGER',
219+
FALSE,
220+
TRUE
221+
),(
222+
'rss',
223+
'transcript_url',
224+
'A link to a transcript or closed captions file for the item.',
225+
'URL',
226+
FALSE,
227+
TRUE
228+
),(
229+
'rss',
230+
'transcript_type',
231+
'The type of the transcript or closed captions file for the item (e.g., "text/plain", "text/html", "text/vtt", "application/json", "application/x-subrip").',
232+
'TEXT',
233+
FALSE,
234+
TRUE
235+
);
236+
237+
-- Insert example(s) for the component
238+
INSERT INTO example (component, description)
239+
VALUES (
240+
'rss',
241+
'
242+
### An RSS channel about SQLPage latest news.
243+
244+
```sql
245+
select ''http_header'' as component, ''application/rss+xml'' as content_type;
246+
select ''shell-empty'' as component;
247+
select
248+
''rss'' as component,
249+
''SQLPage blog'' as title,
250+
''https://sql.ophir.dev/blog.sql'' as link,
251+
''latest news about SQLpage'' as description,
252+
''en'' as language,
253+
''Technology'' as category,
254+
FALSE as explicit,
255+
''https://sql.ophir.dev/favicon.ico'' as image_url,
256+
''Ophir Lojkine'' as author,
257+
''https://github.com/sponsors/lovasoa'' as funding_url,
258+
''episodic'' as type;
259+
select
260+
''Hello everyone !'' as title,
261+
''https://sql.ophir.dev/blog.sql?post=Come%20see%20me%20build%20twitter%20live%20on%20stage%20in%20Prague'' as link,
262+
''If some of you european SQLPagers are around Prague this december, I will be giving a talk about SQLPage at pgconf.eu on December 14th.'' as description,
263+
''http://127.0.0.1:8080/sqlpage_introduction_video.webm'' as enclosure_url,
264+
123456789 as enclosure_length,
265+
''video/webm'' as enclosure_type,
266+
''2023-12-04'' as date;
267+
```
268+
269+
Once you have your rss feed ready, you can submit it to podcast directories like
270+
[Apple Podcasts](https://podcastsconnect.apple.com/my-podcasts),
271+
[Spotify](https://podcasters.spotify.com/),
272+
[Google Podcasts](https://podcastsmanager.google.com/)...
273+
');

sqlpage/templates/rss.handlebars

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<rss version="2.0"
3+
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
4+
xmlns:podcast="https://podcastindex.org/namespace/1.0"
5+
xmlns:atom="http://www.w3.org/2005/Atom"
6+
xmlns:content="http://purl.org/rss/1.0/modules/content/">
7+
<channel>
8+
<title>{{title}}</title>
9+
<link>{{link}}</link>
10+
<description>{{description}}</description>
11+
{{~#if language}}<language>{{language}}</language>{{/if}}
12+
{{~#if category}}<itunes:category text="{{category}}">{{sub_category}}</itunes:category>{{/if}}
13+
<itunes:explicit>{{#if explicit}}true{{else}}false{{/if}}</itunes:explicit>
14+
{{~#if image_url}}<itunes:image href="{{image_url}}" />{{/if}}
15+
{{~#if author}}<itunes:author>{{author}}</itunes:author>{{/if}}
16+
{{~#if copyright}}<copyright>{{copyright}}</copyright>{{/if}}
17+
{{~#if funding_url}}<podcast:funding url="{{funding_url}}">{{funding_text}}</podcast:funding>{{/if}}
18+
{{~#if type}}<itunes:type>{{type}}</itunes:type>{{/if}}
19+
{{~#if complete}}<itunes:complete>yes</itunes:complete>{{/if}}
20+
{{~#if locked}}<podcast:locked>yes</podcast:locked>{{/if}}
21+
{{~#if guid}}<podcast:guid>{{guid}}</podcast:guid>{{/if}}
22+
{{~#if self_link}}<atom:link href="{{self_link}}" rel="self" type="application/rss+xml" />{{/if}}
23+
{{#each_row}}
24+
<item>
25+
<title>{{title}}</title>
26+
<link>{{link}}</link>
27+
<description>{{description}}</description>
28+
{{~#if date}}<pubDate>{{rfc2822_date date}}</pubDate>{{/if}}
29+
{{~#if enclosure_url}}<enclosure length="{{enclosure_length}}" type="{{enclosure_type}}" url="{{enclosure_url}}" />{{/if}}
30+
{{~#if guid}}<guid isPermaLink="false">{{guid}}</guid>{{/if}}
31+
{{~#if episode}}<itunes:episode>{{episode}}</itunes:episode>{{/if}}
32+
{{~#if season}}<itunes:season>{{season}}</itunes:season>{{/if}}
33+
{{~#if episode_type}}<itunes:episodeType>{{episode_type}}</itunes:episodeType>{{/if}}
34+
{{~#if block}}<itunes:block>yes</itunes:block>{{/if}}
35+
{{~#if (not (eq explicit NULL))}}<itunes:explicit>{{#if explicit}}true{{else}}false{{/if}}</itunes:explicit>{{/if}}
36+
{{~#if image_url}}<itunes:image href="{{image_url}}" />{{/if}}
37+
{{~#if duration}}<itunes:duration>{{duration}}</itunes:duration>{{/if}}
38+
{{~#if transcript_url}}<podcast:transcript url="{{transcript_url}}" type="{{transcript_type}}" />{{/if}}
39+
</item>
40+
{{/each_row}}
41+
</channel>
42+
</rss>

sqlpage/templates/shell.handlebars

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
{{#if refresh}}
3535
<meta http-equiv="refresh" content="{{refresh}}">
3636
{{/if}}
37+
{{#if rss}}
38+
<link rel="alternate" type="application/rss+xml" title="{{title}}" href="{{rss}}">
39+
{{/if}}
3740
<meta name="generator" content="SQLPage"/>
3841
</head>
3942

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ pub mod app_config;
77
pub mod file_cache;
88
pub mod filesystem;
99
pub mod render;
10+
pub mod template_helpers;
1011
pub mod templates;
1112
pub mod utils;
1213
pub mod webserver;

0 commit comments

Comments
 (0)