Skip to content
This repository was archived by the owner on Apr 21, 2026. It is now read-only.

Commit acdcd95

Browse files
authored
Merge pull request #29 from paiindustries/feature/blog-rss-feed
implemneted rss feed and fixed blog issues
2 parents e3f2365 + 178e94c commit acdcd95

6 files changed

Lines changed: 123 additions & 15 deletions

File tree

.env-testing

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
LUCEE_EXTENSIONS=465E1E35-2425-4F4E-8B3FAB638BD7280A;name=H2;version=1.3.172
22
cfconfig_adminPassword=commandbox
3+
application_host=http://10.100.10.95:50052
34

45
wheelsdev_host=aura.paiindustries.com
56
wheelsdev_port=1433

app/config/routes.cfm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
.get(name = "Categories", pattern = "blog/Categories", to = "web.BlogController##Categories")
6565
.get(name = "blogArchive", pattern = "blog/[year]/[month]", to = "web.BlogController##Index")
6666
.get(name = "blogs", pattern = "blog/list/[filterType]/[filterValue]", to = "web.BlogController##Index")
67+
.get(name = "blogFeed", pattern = "blog/feed", to = "web.BlogController##feed")
6768
.get(name = "allblogs", pattern = "blog/list", to = "web.BlogController##blogs")
6869
6970
.get(name = "blog-create", pattern = "blog/create", to = "web.BlogController##create")

app/controllers/Controller.cfc

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,61 @@ component extends="wheels.Controller" {
9696
function saveRedirectUrl(url) {
9797
session.redirectAfterLogin = arguments.url;
9898
}
99+
100+
public string function generateMetaDescription(required string htmlContent) {
101+
// Remove headings and non-paragraph wrappers
102+
var contentWithoutHeadings = reReplaceNoCase(arguments.htmlContent, "<h[1-6][^>]*>.*?</h[1-6]>", "", "all");
103+
104+
// Extract only paragraph blocks
105+
var paragraphMatches = reMatchNoCase("<p[^>]*>(.*?)</p>", contentWithoutHeadings);
106+
var cleanText = "";
107+
108+
for (var para in paragraphMatches) {
109+
// Strip inner tags from paragraph content
110+
var plainPara = trim(reReplace(para, "<[^>]*>", "", "all"));
111+
112+
// Skip if the paragraph is too short or looks like a heading
113+
if (len(plainPara) GTE 40) {
114+
cleanText = plainPara;
115+
break;
116+
}
117+
}
118+
119+
// Fallback: use stripped full content if no usable paragraph found
120+
if (cleanText == "") {
121+
cleanText = trim(reReplace(arguments.htmlContent, "<[^>]*>", "", "all"));
122+
}
123+
124+
// Extract up to 2 sentences, avoiding decimals like 3.0 or 2.5
125+
var pattern = "[^.!?]+[.!?]";
126+
var pos = 1;
127+
var meta = "";
128+
var sentences = [];
129+
130+
while (pos LTE len(cleanText)) {
131+
var found = reFind(pattern, cleanText, pos, true);
132+
if (!found.len[1]) break;
133+
134+
var sentence = trim(mid(cleanText, found.pos[1], found.len[1]));
135+
136+
// Skip sentence if it contains decimal numbers (e.g., 3.0)
137+
if (!reFind("\d+\.\d+", sentence)) {
138+
arrayAppend(sentences, sentence);
139+
}
140+
141+
pos += found.len[1];
142+
143+
// Stop after 2 sentences or if length looks good
144+
if (arrayLen(sentences) >= 2 || len(arrayToList(sentences, " ")) >= 160) break;
145+
}
146+
147+
meta = arrayToList(sentences, " ");
148+
149+
// Final fallback if no valid sentences
150+
if (len(meta) == 0) {
151+
meta = left(cleanText, 160);
152+
}
153+
154+
return left(meta, 160);
155+
}
99156
}

app/controllers/web/BlogController.cfc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ component extends="app.Controllers.Controller" {
33

44
// Configuration function
55
function config() {
6-
verifies(except="index,create,store,show,update,destroy,loadCategories,loadStatuses,loadPostTypes,Categories,blogs,comment", params="key", paramsTypes="integer", handler="index");
6+
verifies(except="index,create,store,show,update,destroy,loadCategories,loadStatuses,loadPostTypes,Categories,blogs,comment,feed", params="key", paramsTypes="integer", handler="index");
77
filters(through="restrictAccess", only="create,store,comment");
88
usesLayout("/layout");
99
}
@@ -189,6 +189,14 @@ component extends="app.Controllers.Controller" {
189189
renderPartial(partial="partials/_error");
190190
}
191191

192+
public function feed() {
193+
// Fetch all blogs
194+
blogPosts = model("Blog").findAll(include="User", order="createdAt DESC", limit=20);
195+
196+
// Render the feed view
197+
renderPartial(partial="partials/feed");
198+
}
199+
192200
// Business Logic
193201

194202
private function getAll() {

app/views/layout.cfm

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,13 @@
1515
<!--- Fetch the blog post by slug --->
1616
<cfset post = model("Blog").findOne(where="slug = '#blogSlug#'")>
1717

18-
<cfif structKeyExists(post, "id")>
19-
<cfset pageTitle = post.title & " | CFWheels">
20-
21-
<!-- Generate meta description from content -->
22-
<cfset firstP = reFind("<p[^>]*>(.*?)</p>", post.content, 1, true)>
23-
<cfif arrayLen(firstP.pos) GT 1>
24-
<cfset metaDescription = mid(post.content, firstP.pos[2], firstP.len[2])>
25-
<cfelse>
26-
<cfset cleanContent = reReplace(post.content, "<[^>]*>", "", "all")>
27-
<cfset metaDescription = left(trim(cleanContent), 160)>
28-
</cfif>
18+
<cfif isStruct(post) && structKeyExists(post, "id")>
19+
<cfset metaDescription = this.generateMetaDescription(post.content)>
2920

3021
<cfset ogTitle = post.title>
3122
<cfset ogDescription = metaDescription>
32-
<!--- <cfset ogImage = ''> --->
23+
<cfset ogImage = ''>
3324
<cfelse>
34-
<!-- fallback -->
3525
<cfset pageTitle = "Blog | CFWheels">
3626
<cfset metaDescription = "Explore our latest blogs on CFWheels.">
3727
<cfset ogTitle = pageTitle>
@@ -137,7 +127,7 @@
137127
<li class="nav-item dropdown px-3">
138128
<a href="javascript:void(0)" class="nav-link p-0" id="profilePicDropdown" data-bs-toggle="dropdown" aria-expanded="false">
139129
<cfif !structKeyExists(session, "profilePic") OR session.profilePic == "">
140-
<cfset session.profilePic = "avatar-rounded.webp">
130+
<cfset session.profilePic = "/images/avatar-rounded.webp">
141131
</cfif>
142132
<cfoutput>
143133
#imageTag(source = '#session.profilePic#', alt="user profile pic", height="40", width="40", class="rounded-circle")#
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<cfcontent type="application/rss+xml; charset=utf-8">
2+
<cfheader name="Content-Type" value="application/rss+xml; charset=utf-8">
3+
<?xml version="1.0" encoding="UTF-8"?>
4+
<rss version="2.0"
5+
xmlns:content="http://purl.org/rss/1.0/modules/content/"
6+
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
7+
xmlns:dc="http://purl.org/dc/elements/1.1/"
8+
xmlns:atom="http://www.w3.org/2005/Atom"
9+
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
10+
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
11+
>
12+
<channel>
13+
<cfoutput>
14+
<title>Blog | CFWheels</title>
15+
<atom:link href="#application.env.application_host#/blog/feed/" rel="self" type="application/rss+xml" />
16+
<link>#application.env.application_host#/blog</link>
17+
<description><![CDATA[Build apps quickly with an organized, Ruby on Rails-inspired structure. Get up and running in no time!]]></description>
18+
<!--- <lastBuildDate>#dateFormat(blogPosts[1].updatedAt, "ddd, dd mmm yyyy")# #timeFormat(blogPosts[1].updatedAt, "HH:mm:ss")# +0000</lastBuildDate> --->
19+
<language>en-US</language>
20+
<sy:updatePeriod>hourly</sy:updatePeriod>
21+
<sy:updateFrequency>1</sy:updateFrequency>
22+
<generator>#application.env.application_host#</generator>
23+
24+
<!--- <image>
25+
<url>#application.env.application_host#/images/rss-icon.png</url>
26+
<title>#htmlEdit(blogTitle)#</title>
27+
<link>#application.env.application_host#/blog</link>
28+
<width>32</width>
29+
<height>32</height>
30+
</image>--->
31+
32+
<cfloop query="#blogPosts#">
33+
<item>
34+
<title><![CDATA[#blogPosts.title#]]></title>
35+
<link>#application.env.application_host#/blog/#blogPosts.slug#</link>
36+
<dc:creator><![CDATA[#blogPosts.firstname# #blogPosts.lastname#]]></dc:creator>
37+
<pubDate>#dateFormat(blogPosts.updatedAt, "ddd, dd mmm yyyy")# #timeFormat(blogPosts.updatedAt, "HH:mm:ss")# +0000</pubDate>
38+
<cfset category = this.getCategoriesByBlogid(blogPosts.id)>
39+
<cfloop query="#category#">
40+
<category><![CDATA[#category.name#]]></category>
41+
</cfloop>
42+
43+
<guid isPermaLink="false">#application.env.application_host#/blog/#blogPosts.slug#</guid>
44+
<cfset strippedContent = reReplace(blogPosts.content, "<[^>]*>", "", "all")>
45+
<cfset shortDescription = left(strippedContent, 160)>
46+
<description><![CDATA[#shortDescription#]]></description>
47+
</item>
48+
</cfloop>
49+
</cfoutput>
50+
</channel>
51+
</rss>

0 commit comments

Comments
 (0)