| @@ -38,66 +38,173 @@ Class PostsController_Class | |||
| ' Action: Index | |||
| '--------------------------------------------------------------- | |||
| Public Sub Index() | |||
| ' TODO: Implement Index action | |||
| Response.Write "Index action called" | |||
| Dim posts | |||
| Set posts = PostsRepository().FindAllWhere("IsPublished = 1", "PublishedDate DESC", 0, 20) | |||
| %> | |||
| <!--#include file="../views/Posts/index.asp" --> | |||
| <% | |||
| End Sub | |||
| '--------------------------------------------------------------- | |||
| ' Action: Show | |||
| '--------------------------------------------------------------- | |||
| Public Sub Show(slug) | |||
| ' TODO: Implement Show action | |||
| Response.Write "<strong>Show called</strong><br>" | |||
| Response.Write "Parameters:<br>" | |||
| Response.Write "slug = " & Server.HTMLEncode(CStr(slug)) & "<br>" | |||
| Public Sub Show(ByVal slug) | |||
| Dim matches | |||
| Set matches = PostsRepository().Find(Array("Slug", slug, "IsPublished", 1), Empty) | |||
| If matches.Count = 0 Then | |||
| Response.Status = "404 Not Found" | |||
| %> | |||
| <!--#include file="../views/Error/NotFound.asp" --> | |||
| <% | |||
| Exit Sub | |||
| End If | |||
| Dim post | |||
| Set post = matches.Front() | |||
| %> | |||
| <!--#include file="../views/Posts/show.asp" --> | |||
| <% | |||
| End Sub | |||
| '--------------------------------------------------------------- | |||
| ' Action: New | |||
| '--------------------------------------------------------------- | |||
| Public Sub NewForm() | |||
| ' TODO: Implement NewForm action | |||
| Response.Write "NewForm action called" | |||
| %> | |||
| <!--#include file="../views/Posts/new.asp" --> | |||
| <% | |||
| End Sub | |||
| '--------------------------------------------------------------- | |||
| ' Action: Create | |||
| '--------------------------------------------------------------- | |||
| Public Sub Create() | |||
| ' TODO: Implement Create action | |||
| Response.Write "Create action called" | |||
| Dim title : title = Trim(Request.Form("Title")) | |||
| If Len(title) = 0 Then | |||
| Flash().AddError "Title is required." | |||
| Response.Redirect "/posts/new" | |||
| Exit Sub | |||
| End If | |||
| Dim post | |||
| Set post = New POBO_Posts | |||
| post.Title = title | |||
| post.Summary = Request.Form("Summary") | |||
| post.Body = Request.Form("Body") | |||
| post.CategoryID = FormNumberOrZero(Request.Form("CategoryID")) | |||
| post.Slug = BuildSlug(title) | |||
| post.CreatedDate = Now() | |||
| post.UpdatedDate = Now() | |||
| post.IsPublished = 0 | |||
| PostsRepository().AddNew post | |||
| Flash().Success = "Post created." | |||
| Response.Redirect "/posts" | |||
| End Sub | |||
| '--------------------------------------------------------------- | |||
| ' Action: Edit | |||
| '--------------------------------------------------------------- | |||
| Public Sub Edit(id) | |||
| ' TODO: Implement Edit action | |||
| Response.Write "<strong>Edit called</strong><br>" | |||
| Response.Write "Parameters:<br>" | |||
| Response.Write "id = " & Server.HTMLEncode(CStr(id)) & "<br>" | |||
| Public Sub Edit(ByVal id) | |||
| Dim post | |||
| On Error Resume Next | |||
| Set post = PostsRepository().FindByID(id) | |||
| If Err.Number <> 0 Then | |||
| Err.Clear | |||
| On Error GoTo 0 | |||
| Response.Status = "404 Not Found" | |||
| %> | |||
| <!--#include file="../views/Error/NotFound.asp" --> | |||
| <% | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| %> | |||
| <!--#include file="../views/Posts/edit.asp" --> | |||
| <% | |||
| End Sub | |||
| '--------------------------------------------------------------- | |||
| ' Action: Update | |||
| '--------------------------------------------------------------- | |||
| Public Sub Update(id) | |||
| ' TODO: Implement Update action | |||
| Response.Write "<strong>Update called</strong><br>" | |||
| Response.Write "Parameters:<br>" | |||
| Response.Write "id = " & Server.HTMLEncode(CStr(id)) & "<br>" | |||
| Public Sub Update(ByVal id) | |||
| Dim post | |||
| On Error Resume Next | |||
| Set post = PostsRepository().FindByID(id) | |||
| If Err.Number <> 0 Then | |||
| Err.Clear | |||
| On Error GoTo 0 | |||
| Response.Status = "404 Not Found" | |||
| %> | |||
| <!--#include file="../views/Error/NotFound.asp" --> | |||
| <% | |||
| Exit Sub | |||
| End If | |||
| On Error GoTo 0 | |||
| Dim title : title = Trim(Request.Form("Title")) | |||
| If Len(title) = 0 Then | |||
| Flash().AddError "Title is required." | |||
| Response.Redirect "/posts/" & Server.URLEncode(CStr(id)) & "/edit" | |||
| Exit Sub | |||
| End If | |||
| post.Title = title | |||
| post.Summary = Request.Form("Summary") | |||
| post.Body = Request.Form("Body") | |||
| post.CategoryID = FormNumberOrZero(Request.Form("CategoryID")) | |||
| post.Slug = BuildSlug(title) | |||
| post.UpdatedDate = Now() | |||
| PostsRepository().Update post | |||
| Flash().Success = "Post updated." | |||
| Response.Redirect "/posts" | |||
| End Sub | |||
| '--------------------------------------------------------------- | |||
| ' Action: Delete | |||
| '--------------------------------------------------------------- | |||
| Public Sub Delete(id) | |||
| ' TODO: Implement Delete action | |||
| Response.Write "<strong>Delete called</strong><br>" | |||
| Response.Write "Parameters:<br>" | |||
| Response.Write "id = " & Server.HTMLEncode(CStr(id)) & "<br>" | |||
| Public Sub Delete(ByVal id) | |||
| PostsRepository().Delete id | |||
| Flash().Success = "Post deleted." | |||
| Response.Redirect "/posts" | |||
| End Sub | |||
| Private Function FormNumberOrZero(ByVal value) | |||
| If IsNumeric(value) Then | |||
| FormNumberOrZero = CLng(value) | |||
| Else | |||
| FormNumberOrZero = 0 | |||
| End If | |||
| End Function | |||
| Private Function BuildSlug(ByVal value) | |||
| Dim raw : raw = LCase(Trim(CStr(value))) | |||
| Dim i, ch, slug, previousDash | |||
| slug = "" | |||
| previousDash = False | |||
| For i = 1 To Len(raw) | |||
| ch = Mid(raw, i, 1) | |||
| If (ch >= "a" And ch <= "z") Or (ch >= "0" And ch <= "9") Then | |||
| slug = slug & ch | |||
| previousDash = False | |||
| ElseIf Not previousDash And Len(slug) > 0 Then | |||
| slug = slug & "-" | |||
| previousDash = True | |||
| End If | |||
| Next | |||
| Do While Right(slug, 1) = "-" | |||
| slug = Left(slug, Len(slug) - 1) | |||
| Loop | |||
| If Len(slug) = 0 Then slug = "post" | |||
| BuildSlug = slug | |||
| End Function | |||
| End Class | |||
| ' Singleton instance | |||
| @@ -27,6 +27,54 @@ Class PostsRepository_Class | |||
| Set GetAll = Find(Empty, orderBy) | |||
| End Function | |||
| Public Function FindAllWhere(ByVal where_clause, ByVal order_by, ByVal offset, ByVal limit) | |||
| Dim sql : sql = "Select [Body], [CategoryID], [CreatedDate], [IsPublished], [PostID], [PublishedDate], [Slug], [Summary], [Title], [UpdatedDate] FROM [Posts]" | |||
| Dim whereText : whereText = "" | |||
| If Not IsEmpty(where_clause) Then | |||
| If Not IsNull(where_clause) Then whereText = Trim(CStr(where_clause)) | |||
| End If | |||
| If Len(whereText) > 0 Then | |||
| sql = sql & " WHERE " & whereText | |||
| End If | |||
| sql = sql & BuildOrderBy(order_by, "[PostID]") | |||
| Dim offsetCount, limitCount | |||
| If IsNumeric(offset) Then | |||
| offsetCount = CLng(offset) | |||
| Else | |||
| offsetCount = 0 | |||
| End If | |||
| If offsetCount < 0 Then offsetCount = 0 | |||
| If IsNumeric(limit) Then | |||
| limitCount = CLng(limit) | |||
| Else | |||
| limitCount = 0 | |||
| End If | |||
| Dim rs : Set rs = DAL.Query(sql, Empty) | |||
| Dim list : Set list = new LinkedList_Class | |||
| Dim skipped, added | |||
| skipped = 0 | |||
| added = 0 | |||
| Do Until rs.EOF | |||
| If skipped < offsetCount Then | |||
| skipped = skipped + 1 | |||
| ElseIf limitCount <= 0 Or added < limitCount Then | |||
| list.Push Automapper.AutoMap(rs, "POBO_Posts") | |||
| added = added + 1 | |||
| End If | |||
| If limitCount > 0 And added >= limitCount Then Exit Do | |||
| rs.MoveNext | |||
| Loop | |||
| Set FindAllWhere = list | |||
| Destroy rs | |||
| End Function | |||
| Public Function Find(where_kvarray, order_string_or_array) | |||
| Dim sql : sql = "Select [Body], [CategoryID], [CreatedDate], [IsPublished], [PostID], [PublishedDate], [Slug], [Summary], [Title], [UpdatedDate] FROM [Posts]" | |||
| Dim where_keys, where_values, i | |||
| @@ -149,21 +197,65 @@ Class PostsRepository_Class | |||
| QI = "[" & Replace(CStr(name), "]", "]]") & "]" | |||
| End Function | |||
| Private Function BuildOrderBy(orderArg, defaultCol) | |||
| Private Function BuildOrderBy(ByVal orderArg, ByVal defaultCol) | |||
| Dim s : s = "" | |||
| If IsEmpty(orderArg) Or IsNull(orderArg) Or orderArg = "" Then | |||
| s = " ORDER BY " & defaultCol & " ASC" | |||
| ElseIf IsArray(orderArg) Then | |||
| If IsArray(orderArg) Then | |||
| Dim i : s = " ORDER BY " | |||
| For i = 0 To UBound(orderArg) | |||
| If i > 0 Then s = s & ", " | |||
| s = s & QI(orderArg(i)) | |||
| Next | |||
| ElseIf IsEmpty(orderArg) Then | |||
| s = " ORDER BY " & defaultCol & " ASC" | |||
| ElseIf IsNull(orderArg) Then | |||
| s = " ORDER BY " & defaultCol & " ASC" | |||
| ElseIf CStr(orderArg) = "" Then | |||
| s = " ORDER BY " & defaultCol & " ASC" | |||
| ElseIf IsSafeOrderExpression(orderArg) Then | |||
| Dim parts : parts = Split(Trim(CStr(orderArg)), " ") | |||
| s = " ORDER BY " & QI(parts(0)) | |||
| If UBound(parts) = 1 Then s = s & " " & UCase(parts(1)) | |||
| Else | |||
| s = " ORDER BY " & QI(orderArg) | |||
| End If | |||
| BuildOrderBy = s | |||
| End Function | |||
| Private Function IsSafeOrderExpression(ByVal orderArg) | |||
| Dim raw : raw = Trim(CStr(orderArg)) | |||
| Dim parts : parts = Split(raw, " ") | |||
| IsSafeOrderExpression = False | |||
| If UBound(parts) < 0 Or UBound(parts) > 1 Then Exit Function | |||
| If Not IsSafeIdentifier(parts(0)) Then Exit Function | |||
| If UBound(parts) = 1 Then | |||
| If UCase(parts(1)) <> "ASC" And UCase(parts(1)) <> "DESC" Then Exit Function | |||
| End If | |||
| IsSafeOrderExpression = True | |||
| End Function | |||
| Private Function IsSafeIdentifier(ByVal value) | |||
| Dim i, ch | |||
| value = CStr(value) | |||
| If Len(value) = 0 Then | |||
| IsSafeIdentifier = False | |||
| Exit Function | |||
| End If | |||
| For i = 1 To Len(value) | |||
| ch = Mid(value, i, 1) | |||
| If Not ((ch >= "a" And ch <= "z") Or _ | |||
| (ch >= "A" And ch <= "Z") Or _ | |||
| (ch >= "0" And ch <= "9") Or _ | |||
| ch = "_") Then | |||
| IsSafeIdentifier = False | |||
| Exit Function | |||
| End If | |||
| Next | |||
| IsSafeIdentifier = True | |||
| End Function | |||
| End Class | |||
| Dim PostsRepository__Singleton | |||
| @@ -0,0 +1,40 @@ | |||
| <div class="row"> | |||
| <div class="col-lg-8"> | |||
| <div class="card shadow-sm"> | |||
| <div class="card-body"> | |||
| <h1 class="h3 mb-4">Edit Post</h1> | |||
| <form method="post" action="/posts/<%= H(post.PostID) %>"> | |||
| <div class="mb-3"> | |||
| <label class="form-label" for="Title">Title</label> | |||
| <input class="form-control" type="text" id="Title" name="Title" value="<%= H(post.Title) %>" required> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label class="form-label" for="Summary">Summary</label> | |||
| <textarea class="form-control" id="Summary" name="Summary" rows="3"><%= H(post.Summary) %></textarea> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label class="form-label" for="Body">Body</label> | |||
| <textarea class="form-control" id="Body" name="Body" rows="10" required><%= H(post.Body) %></textarea> | |||
| </div> | |||
| <div class="mb-4"> | |||
| <label class="form-label" for="CategoryID">Category ID</label> | |||
| <input class="form-control" type="number" id="CategoryID" name="CategoryID" min="0" value="<%= H(post.CategoryID) %>"> | |||
| </div> | |||
| <div class="d-flex flex-wrap gap-2"> | |||
| <button class="btn btn-primary" type="submit">Update Post</button> | |||
| <a class="btn btn-outline-secondary" href="/posts">Cancel</a> | |||
| </div> | |||
| </form> | |||
| <form method="post" action="/posts/<%= H(post.PostID) %>/delete" class="mt-3"> | |||
| <button class="btn btn-outline-danger" type="submit">Delete Post</button> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -0,0 +1,62 @@ | |||
| <div class="d-flex align-items-center justify-content-between mb-4"> | |||
| <div> | |||
| <h1 class="h3 mb-1">Posts</h1> | |||
| <p class="text-muted mb-0">Published articles from ASPBlogBrainOrdure.</p> | |||
| </div> | |||
| <a class="btn btn-primary" href="/posts/new">New Post</a> | |||
| </div> | |||
| <% | |||
| Dim postIter, postItem, shownCount | |||
| Set postIter = posts.Iterator() | |||
| shownCount = 0 | |||
| If posts.Count = 0 Then | |||
| %> | |||
| <div class="alert alert-secondary">No published posts are available yet.</div> | |||
| <% | |||
| Else | |||
| %> | |||
| <div class="row gy-3"> | |||
| <% | |||
| Do While postIter.HasNext And shownCount < 20 | |||
| Set postItem = postIter.GetNext() | |||
| shownCount = shownCount + 1 | |||
| %> | |||
| <div class="col-12"> | |||
| <article class="card shadow-sm"> | |||
| <div class="card-body"> | |||
| <div class="d-flex flex-column flex-md-row justify-content-between gap-2"> | |||
| <h2 class="h5 mb-1"> | |||
| <a href="/posts/<%= Server.URLEncode(CStr(postItem.Slug)) %>" class="text-decoration-none"> | |||
| <%= H(postItem.Title) %> | |||
| </a> | |||
| </h2> | |||
| <% | |||
| Dim publishedText | |||
| publishedText = "" | |||
| If IsDate(postItem.PublishedDate) Then | |||
| If CDate(postItem.PublishedDate) > #1/1/1970# Then | |||
| publishedText = FormatDateTime(postItem.PublishedDate, vbLongDate) | |||
| End If | |||
| End If | |||
| If Len(publishedText) > 0 Then | |||
| %> | |||
| <span class="small text-muted"><%= H(publishedText) %></span> | |||
| <% | |||
| End If | |||
| %> | |||
| </div> | |||
| <p class="text-muted mb-3"><%= H(postItem.Summary) %></p> | |||
| <a class="btn btn-sm btn-outline-primary" href="/posts/<%= Server.URLEncode(CStr(postItem.Slug)) %>">Read</a> | |||
| </div> | |||
| </article> | |||
| </div> | |||
| <% | |||
| Loop | |||
| %> | |||
| </div> | |||
| <% | |||
| End If | |||
| %> | |||
| @@ -0,0 +1,36 @@ | |||
| <div class="row"> | |||
| <div class="col-lg-8"> | |||
| <div class="card shadow-sm"> | |||
| <div class="card-body"> | |||
| <h1 class="h3 mb-4">New Post</h1> | |||
| <form method="post" action="/posts"> | |||
| <div class="mb-3"> | |||
| <label class="form-label" for="Title">Title</label> | |||
| <input class="form-control" type="text" id="Title" name="Title" required> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label class="form-label" for="Summary">Summary</label> | |||
| <textarea class="form-control" id="Summary" name="Summary" rows="3"></textarea> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label class="form-label" for="Body">Body</label> | |||
| <textarea class="form-control" id="Body" name="Body" rows="10" required></textarea> | |||
| </div> | |||
| <div class="mb-4"> | |||
| <label class="form-label" for="CategoryID">Category ID</label> | |||
| <input class="form-control" type="number" id="CategoryID" name="CategoryID" min="0" value="0"> | |||
| </div> | |||
| <div class="d-flex gap-2"> | |||
| <button class="btn btn-primary" type="submit">Create Post</button> | |||
| <a class="btn btn-outline-secondary" href="/posts">Cancel</a> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -0,0 +1,33 @@ | |||
| <article class="card shadow-sm"> | |||
| <div class="card-body"> | |||
| <div class="mb-3"> | |||
| <a href="/posts" class="small text-decoration-none">← Back to posts</a> | |||
| </div> | |||
| <h1 class="h2 mb-2"><%= H(post.Title) %></h1> | |||
| <% | |||
| Dim publishedText | |||
| publishedText = "" | |||
| If IsDate(post.PublishedDate) Then | |||
| If CDate(post.PublishedDate) > #1/1/1970# Then | |||
| publishedText = FormatDateTime(post.PublishedDate, vbLongDate) | |||
| End If | |||
| End If | |||
| If Len(publishedText) > 0 Then | |||
| %> | |||
| <p class="text-muted"><%= H(publishedText) %></p> | |||
| <% | |||
| End If | |||
| Dim postBody | |||
| postBody = H(post.Body) | |||
| postBody = Replace(postBody, vbCrLf, "<br>") | |||
| postBody = Replace(postBody, vbCr, "<br>") | |||
| postBody = Replace(postBody, vbLf, "<br>") | |||
| %> | |||
| <div class="fs-5 lh-lg"><%= postBody %></div> | |||
| </div> | |||
| </article> | |||
Powered by TurnKey Linux.