From d98347fc9b5f6f76f5cd2723a8c7ed15ed69fa31 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Sat, 18 Mar 2017 03:06:20 +0000 Subject: [PATCH 01/21] Add published column to blog post table --- .../Controllers/BlogAdminController.swift | 2 +- Sources/SteamPress/Models/BlogPost.swift | 11 +++++++++-- Sources/SteamPress/Models/BlogPostDraft.swift | 13 +++++++++++++ Sources/SteamPress/Provider.swift | 1 + Tests/SteamPressTests/BlogControllerTests.swift | 4 ++-- Tests/SteamPressTests/Helpers/TestDataBuilder.swift | 6 +++--- 6 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 Sources/SteamPress/Models/BlogPostDraft.swift diff --git a/Sources/SteamPress/Controllers/BlogAdminController.swift b/Sources/SteamPress/Controllers/BlogAdminController.swift index 1555eadf..2a1ed07f 100644 --- a/Sources/SteamPress/Controllers/BlogAdminController.swift +++ b/Sources/SteamPress/Controllers/BlogAdminController.swift @@ -81,7 +81,7 @@ struct BlogAdminController { // Make sure slugUrl is unique slugUrl = BlogPost.generateUniqueSlugUrl(from: slugUrl) - var newPost = BlogPost(title: title, contents: contents, author: user, creationDate: creationDate, slugUrl: slugUrl) + var newPost = BlogPost(title: title, contents: contents, author: user, creationDate: creationDate, slugUrl: slugUrl, published: true) try newPost.save() // Save the tags diff --git a/Sources/SteamPress/Models/BlogPost.swift b/Sources/SteamPress/Models/BlogPost.swift index e8f8a0e8..227f8a51 100644 --- a/Sources/SteamPress/Models/BlogPost.swift +++ b/Sources/SteamPress/Models/BlogPost.swift @@ -14,8 +14,9 @@ public class BlogPost: Model { public var created: Date public var lastEdited: Date? public var slugUrl: String + public var published: Bool - init(title: String, contents: String, author: BlogUser, creationDate: Date, slugUrl: String) { + init(title: String, contents: String, author: BlogUser, creationDate: Date, slugUrl: String, published: Bool) { self.id = nil self.title = title self.contents = contents @@ -23,6 +24,7 @@ public class BlogPost: Model { self.created = creationDate self.slugUrl = BlogPost.generateUniqueSlugUrl(from: slugUrl) self.lastEdited = nil + self.published = published } required public init(node: Node, in context: Context) throws { @@ -31,6 +33,7 @@ public class BlogPost: Model { contents = try node.extract("contents") author = try node.extract("bloguser_id") slugUrl = try node.extract("slug_url") + published = try node.extract("published") let createdTime: Double = try node.extract("created") let lastEditedTime: Double? = try? node.extract("last_edited") @@ -52,7 +55,8 @@ extension BlogPost: NodeRepresentable { "contents": contents, "bloguser_id": author, "created": createdTime, - "slug_url": slugUrl + "slug_url": slugUrl, + "published": published, ]) if let lastEdited = lastEdited { @@ -74,6 +78,7 @@ extension BlogPost: NodeRepresentable { "author_username": try getAuthor()?.username.makeNode(), "short_snippet": shortSnippet().makeNode(), "created_date": createdDate.makeNode(), + "published": published, "slug_url": slugUrl ]) case BlogPostContext.longSnippet: @@ -84,6 +89,7 @@ extension BlogPost: NodeRepresentable { "author_username": try getAuthor()?.username.makeNode(), "long_snippet": longSnippet().makeNode(), "created_date": createdDate.makeNode(), + "published": published, "slug_url": slugUrl ]) @@ -138,6 +144,7 @@ extension BlogPost { posts.double("created") posts.double("last_edited", optional: true) posts.string("slug_url", unique: true) + posts.bool("published") } } diff --git a/Sources/SteamPress/Models/BlogPostDraft.swift b/Sources/SteamPress/Models/BlogPostDraft.swift new file mode 100644 index 00000000..8a701e65 --- /dev/null +++ b/Sources/SteamPress/Models/BlogPostDraft.swift @@ -0,0 +1,13 @@ +import Fluent + +struct BlogPostDraft: Preparation { + static func prepare(_ database: Database) throws { + try database.modify(BlogPost.entity, closure: { blogPost in + blogPost.bool("published", optional: false, default: true) + }) + } + + static func revert(_ database: Database) throws { + + } +} diff --git a/Sources/SteamPress/Provider.swift b/Sources/SteamPress/Provider.swift index 003fae86..1d1a6d57 100644 --- a/Sources/SteamPress/Provider.swift +++ b/Sources/SteamPress/Provider.swift @@ -34,6 +34,7 @@ public struct Provider: Vapor.Provider { drop.preparations.append(BlogUser.self) drop.preparations.append(BlogTag.self) drop.preparations.append(Pivot.self) + drop.preparations.append(BlogPostDraft.self) // Middleware let authMiddleware = BlogAuthMiddleware() diff --git a/Tests/SteamPressTests/BlogControllerTests.swift b/Tests/SteamPressTests/BlogControllerTests.swift index 218a2dac..27bb1210 100644 --- a/Tests/SteamPressTests/BlogControllerTests.swift +++ b/Tests/SteamPressTests/BlogControllerTests.swift @@ -83,7 +83,7 @@ class BlogControllerTests: XCTestCase { user = BlogUser(name: "Luke", username: "luke", password: "1234") } try user.save() - post = BlogPost(title: "Test Path", contents: "A long time ago", author: user, creationDate: Date(), slugUrl: "test-path") + post = BlogPost(title: "Test Path", contents: "A long time ago", author: user, creationDate: Date(), slugUrl: "test-path", published: true) try post.save() try BlogTag.addTag("tatooine", to: post) @@ -92,7 +92,7 @@ class BlogControllerTests: XCTestCase { func testBlogIndexGetsPostsInReverseOrder() throws { try setupDrop() - var post2 = BlogPost(title: "A New Path", contents: "In a galaxy far, far, away", author: user, creationDate: Date(), slugUrl: "a-new-path") + var post2 = BlogPost(title: "A New Path", contents: "In a galaxy far, far, away", author: user, creationDate: Date(), slugUrl: "a-new-path", published: true) try post2.save() _ = try drop.respond(to: blogIndexRequest) diff --git a/Tests/SteamPressTests/Helpers/TestDataBuilder.swift b/Tests/SteamPressTests/Helpers/TestDataBuilder.swift index 28a75e21..c1a771b4 100644 --- a/Tests/SteamPressTests/Helpers/TestDataBuilder.swift +++ b/Tests/SteamPressTests/Helpers/TestDataBuilder.swift @@ -10,17 +10,17 @@ struct TestDataBuilder { } static func anyPost(slugUrl: String = "some-exciting-title", author: BlogUser = TestDataBuilder.anyUser(), creationDate: Date = Date()) -> BlogPost { - return BlogPost(title: "An Exciting Post!", contents: "

This is a blog post

", author: author, creationDate: creationDate, slugUrl: slugUrl) + return BlogPost(title: "An Exciting Post!", contents: "

This is a blog post

", author: author, creationDate: creationDate, slugUrl: slugUrl, published: true) } static func anyPostWithImage(author: BlogUser = TestDataBuilder.anyUser()) -> BlogPost { let contents = "# Welcome to SteamPress! SteamPress started out as an idea - after all, I was porting sites and backends over to Swift and would like to have a blog as well. Being early days for Server-Side Swift, and embracing Vapor, there wasn't anything available to put a blog on my site, so I did what any self-respecting engineer would do - I made one! Besides, what better way to learn a framework than build a blog!\n\nI plan to put some more posts up going into how I actually wrote SteamPress, going into some Vapor basics like Authentication and other popular #help topics on [Slack](qutheory.slack.com) (I probably need to rewrite a lot of it properly first!) either on here or on https://geeks.brokenhands.io, which will be the engineering site for Broken Hands, which is what a lot of future projects I have planned will be under.\n\n![Octodex](https://octodex.github.com/images/privateinvestocat.jpg)\n\nHappy blogging!\n\nTim\n" - return BlogPost(title: "An Exciting Post With Image", contents: contents, author: author, creationDate: Date(), slugUrl: "an-exciting-post-with-image") + return BlogPost(title: "An Exciting Post With Image", contents: contents, author: author, creationDate: Date(), slugUrl: "an-exciting-post-with-image", published: true) } static func anyLongPost() -> BlogPost { let title = "Introduction To Steampress" - return BlogPost(title: title, contents: longContents, author: anyUser(), creationDate: Date(), slugUrl: title) + return BlogPost(title: title, contents: longContents, author: anyUser(), creationDate: Date(), slugUrl: title, published: true) } } From da9b7beaedfa7902663c67c1d15c7c60eba1236a Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Sun, 19 Mar 2017 13:36:28 +0000 Subject: [PATCH 02/21] Save as draft/publsihed depending on POST data --- README.md | 6 ++---- .../Controllers/BlogAdminController.swift | 14 +++++++++++++- Tests/SteamPressTests/LeafViewFactoryTests.swift | 1 + 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0952461c..c9e382f0 100644 --- a/README.md +++ b/README.md @@ -330,9 +330,7 @@ I anticipate SteamPress staying on a version 0 for some time, whilst some of the On the roadmap we have: -* Image uploading - you can link to images easily but can't upload any without redeploying the site - I may implement some functionality for this depending on whether people want images going to the same site as the code or something like an S3 bucket (I'm leaning towards the S3 option so answers on a postcard!) -* Blog drafts - it would be nice not to publish posts until you want to -* Sitemap/RSS feed - again for SEO -* AMP endpoints for posts +* Sitemap/RSS feed for SEO +* AMP/Facebook instant articles endpoints for posts * Searching through the blog * Saving state when logging in - if you go to a page (e.g. edit post) but need to be logged in, it would be great if you could head back to that page once logged in. Also, if you have edited a post and your session expires before you post it, wouldn't it be great if it remembered everything! diff --git a/Sources/SteamPress/Controllers/BlogAdminController.swift b/Sources/SteamPress/Controllers/BlogAdminController.swift index 2a1ed07f..32191011 100644 --- a/Sources/SteamPress/Controllers/BlogAdminController.swift +++ b/Sources/SteamPress/Controllers/BlogAdminController.swift @@ -57,6 +57,8 @@ struct BlogAdminController { let rawContents = request.data["inputPostContents"]?.string let rawTags = request.data["inputTags"]?.array let rawSlugUrl = request.data["inputSlugUrl"]?.string + let draft = request.data["draft"]?.string + let publish = request.data["publish"]?.string // I must be able to inline all of this var tagsArray: [Node] = [] @@ -67,6 +69,10 @@ struct BlogAdminController { else if let tagsStringArray = rawTags as? [String] { tagsArray = tagsStringArray.map { $0.makeNode() } } + + if draft == nil && publish == nil { + throw Abort.badRequest + } if let createPostErrors = validatePostCreation(title: rawTitle, contents: rawContents, slugUrl: rawSlugUrl) { return try viewFactory.createBlogPostView(uri: request.uri, errors: createPostErrors, title: rawTitle, contents: rawContents, slugUrl: rawSlugUrl, tags: tagsArray, isEditing: false, postToEdit: nil) @@ -80,8 +86,14 @@ struct BlogAdminController { // Make sure slugUrl is unique slugUrl = BlogPost.generateUniqueSlugUrl(from: slugUrl) + + var published = false + + if let _ = publish { + published = true + } - var newPost = BlogPost(title: title, contents: contents, author: user, creationDate: creationDate, slugUrl: slugUrl, published: true) + var newPost = BlogPost(title: title, contents: contents, author: user, creationDate: creationDate, slugUrl: slugUrl, published: published) try newPost.save() // Save the tags diff --git a/Tests/SteamPressTests/LeafViewFactoryTests.swift b/Tests/SteamPressTests/LeafViewFactoryTests.swift index 9cd550df..4a5ffcdc 100644 --- a/Tests/SteamPressTests/LeafViewFactoryTests.swift +++ b/Tests/SteamPressTests/LeafViewFactoryTests.swift @@ -541,6 +541,7 @@ class LeafViewFactoryTests: XCTestCase { XCTAssertNil(viewRenderer.capturedContext?["tagsSupplied"]) XCTAssertEqual(viewRenderer.leafPath, "blog/admin/createPost") XCTAssertTrue((viewRenderer.capturedContext?["createBlogPostPage"]?.bool) ?? false) + XCTAssertTrue((viewRenderer.capturedContext?["draft"]?.bool) ?? false) XCTAssertNil(viewRenderer.capturedContext?["editing"]) } From 5f203250128bf0bf5a15a732171803c02ea5a73c Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Sun, 19 Mar 2017 13:42:44 +0000 Subject: [PATCH 03/21] Pass draft into create post leaf --- Sources/SteamPress/Controllers/BlogAdminController.swift | 8 ++++---- Sources/SteamPress/Views/LeafViewFactory.swift | 6 +++++- Sources/SteamPress/Views/ViewFactory.swift | 2 +- Tests/SteamPressTests/BlogControllerTests.swift | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Sources/SteamPress/Controllers/BlogAdminController.swift b/Sources/SteamPress/Controllers/BlogAdminController.swift index 32191011..c54398d4 100644 --- a/Sources/SteamPress/Controllers/BlogAdminController.swift +++ b/Sources/SteamPress/Controllers/BlogAdminController.swift @@ -49,7 +49,7 @@ struct BlogAdminController { // MARK: - Blog Posts handlers func createPostHandler(_ request: Request) throws -> ResponseRepresentable { - return try viewFactory.createBlogPostView(uri: request.uri, errors: nil, title: nil, contents: nil, slugUrl: nil, tags: nil, isEditing: false, postToEdit: nil) + return try viewFactory.createBlogPostView(uri: request.uri, errors: nil, title: nil, contents: nil, slugUrl: nil, tags: nil, isEditing: false, postToEdit: nil, draft: true) } func createPostPostHandler(_ request: Request) throws -> ResponseRepresentable { @@ -75,7 +75,7 @@ struct BlogAdminController { } if let createPostErrors = validatePostCreation(title: rawTitle, contents: rawContents, slugUrl: rawSlugUrl) { - return try viewFactory.createBlogPostView(uri: request.uri, errors: createPostErrors, title: rawTitle, contents: rawContents, slugUrl: rawSlugUrl, tags: tagsArray, isEditing: false, postToEdit: nil) + return try viewFactory.createBlogPostView(uri: request.uri, errors: createPostErrors, title: rawTitle, contents: rawContents, slugUrl: rawSlugUrl, tags: tagsArray, isEditing: false, postToEdit: nil, draft: true) } guard let user = try request.auth.user() as? BlogUser, let title = rawTitle, let contents = rawContents, var slugUrl = rawSlugUrl else { @@ -128,7 +128,7 @@ struct BlogAdminController { func editPostHandler(request: Request, post: BlogPost) throws -> ResponseRepresentable { let tags = try post.tags() let tagsArray: [Node] = tags.map { $0.name.makeNode() } - return try viewFactory.createBlogPostView(uri: request.uri, errors: nil, title: post.title, contents: post.contents, slugUrl: post.slugUrl, tags: tagsArray, isEditing: true, postToEdit: post) + return try viewFactory.createBlogPostView(uri: request.uri, errors: nil, title: post.title, contents: post.contents, slugUrl: post.slugUrl, tags: tagsArray, isEditing: true, postToEdit: post, draft: false) } func editPostPostHandler(request: Request, post: BlogPost) throws -> ResponseRepresentable { @@ -147,7 +147,7 @@ struct BlogAdminController { } if let errors = validatePostCreation(title: rawTitle, contents: rawContents, slugUrl: rawSlugUrl) { - return try viewFactory.createBlogPostView(uri: request.uri, errors: errors, title: rawTitle, contents: rawContents, slugUrl: rawSlugUrl, tags: tagsArray, isEditing: true, postToEdit: post) + return try viewFactory.createBlogPostView(uri: request.uri, errors: errors, title: rawTitle, contents: rawContents, slugUrl: rawSlugUrl, tags: tagsArray, isEditing: true, postToEdit: post, draft: false) } guard let title = rawTitle, let contents = rawContents, let slugUrl = rawSlugUrl else { diff --git a/Sources/SteamPress/Views/LeafViewFactory.swift b/Sources/SteamPress/Views/LeafViewFactory.swift index 52f2a06f..654191ac 100644 --- a/Sources/SteamPress/Views/LeafViewFactory.swift +++ b/Sources/SteamPress/Views/LeafViewFactory.swift @@ -11,7 +11,7 @@ struct LeafViewFactory: ViewFactory { // MARK: - Admin Controller Views - func createBlogPostView(uri: URI, errors: [String]? = nil, title: String? = nil, contents: String? = nil, slugUrl: String? = nil, tags: [Vapor.Node]? = nil, isEditing: Bool = false, postToEdit: BlogPost? = nil) throws -> View { + func createBlogPostView(uri: URI, errors: [String]? = nil, title: String? = nil, contents: String? = nil, slugUrl: String? = nil, tags: [Vapor.Node]? = nil, isEditing: Bool = false, postToEdit: BlogPost? = nil, draft: Bool = true) throws -> View { let titleError = (title == nil || (title?.isWhitespace())!) && errors != nil let contentsError = (contents == nil || (contents?.isWhitespace())!) && errors != nil @@ -52,6 +52,10 @@ struct LeafViewFactory: ViewFactory { if let tagsSupplied = tags, tagsSupplied.count > 0 { parameters["tagsSupplied"] = try tagsSupplied.makeNode() } + + if draft { + parameters["draft"] = true.makeNode() + } if isEditing { parameters["editing"] = isEditing.makeNode() diff --git a/Sources/SteamPress/Views/ViewFactory.swift b/Sources/SteamPress/Views/ViewFactory.swift index 26522beb..113924a2 100644 --- a/Sources/SteamPress/Views/ViewFactory.swift +++ b/Sources/SteamPress/Views/ViewFactory.swift @@ -5,7 +5,7 @@ import Paginator protocol ViewFactory { // MARK: - Admin Controller - func createBlogPostView(uri: URI, errors: [String]?, title: String?, contents: String?, slugUrl: String?, tags: [Node]?, isEditing: Bool, postToEdit: BlogPost?) throws -> View + func createBlogPostView(uri: URI, errors: [String]?, title: String?, contents: String?, slugUrl: String?, tags: [Node]?, isEditing: Bool, postToEdit: BlogPost?, draft: Bool) throws -> View func createUserView(editing: Bool, errors: [String]?, name: String?, username: String?, passwordError: Bool?, confirmPasswordError: Bool?, resetPasswordRequired: Bool?, userId: Node?) throws -> View func createLoginView(loginWarning: Bool, errors: [String]?, username: String?, password: String?) throws -> View func createBlogAdminView(errors: [String]?) throws -> View diff --git a/Tests/SteamPressTests/BlogControllerTests.swift b/Tests/SteamPressTests/BlogControllerTests.swift index 27bb1210..c74d0caf 100644 --- a/Tests/SteamPressTests/BlogControllerTests.swift +++ b/Tests/SteamPressTests/BlogControllerTests.swift @@ -375,7 +375,7 @@ import Paginator class CapturingViewFactory: ViewFactory { - func createBlogPostView(uri: URI, errors: [String]?, title: String?, contents: String?, slugUrl: String?, tags: [Node]?, isEditing: Bool, postToEdit: BlogPost?) throws -> View { + func createBlogPostView(uri: URI, errors: [String]?, title: String?, contents: String?, slugUrl: String?, tags: [Node]?, isEditing: Bool, postToEdit: BlogPost?, draft: Bool) throws -> View { return View(data: try "Test".makeBytes()) } From e0503bf0bd1ff7b72bdd34b15d3d0d5e29126824 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Sun, 19 Mar 2017 13:54:25 +0000 Subject: [PATCH 04/21] Use correct POST name for saving a draft --- Sources/SteamPress/Controllers/BlogAdminController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SteamPress/Controllers/BlogAdminController.swift b/Sources/SteamPress/Controllers/BlogAdminController.swift index c54398d4..90aa7564 100644 --- a/Sources/SteamPress/Controllers/BlogAdminController.swift +++ b/Sources/SteamPress/Controllers/BlogAdminController.swift @@ -57,7 +57,7 @@ struct BlogAdminController { let rawContents = request.data["inputPostContents"]?.string let rawTags = request.data["inputTags"]?.array let rawSlugUrl = request.data["inputSlugUrl"]?.string - let draft = request.data["draft"]?.string + let draft = request.data["save-draft"]?.string let publish = request.data["publish"]?.string // I must be able to inline all of this From b023c4f7bddb8a206936579e6ef3fe0405644b04 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Sun, 19 Mar 2017 14:01:38 +0000 Subject: [PATCH 05/21] Mark edit post page with draft correctly --- Sources/SteamPress/Controllers/BlogAdminController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SteamPress/Controllers/BlogAdminController.swift b/Sources/SteamPress/Controllers/BlogAdminController.swift index 90aa7564..ff18a0fd 100644 --- a/Sources/SteamPress/Controllers/BlogAdminController.swift +++ b/Sources/SteamPress/Controllers/BlogAdminController.swift @@ -128,7 +128,7 @@ struct BlogAdminController { func editPostHandler(request: Request, post: BlogPost) throws -> ResponseRepresentable { let tags = try post.tags() let tagsArray: [Node] = tags.map { $0.name.makeNode() } - return try viewFactory.createBlogPostView(uri: request.uri, errors: nil, title: post.title, contents: post.contents, slugUrl: post.slugUrl, tags: tagsArray, isEditing: true, postToEdit: post, draft: false) + return try viewFactory.createBlogPostView(uri: request.uri, errors: nil, title: post.title, contents: post.contents, slugUrl: post.slugUrl, tags: tagsArray, isEditing: true, postToEdit: post, draft: !post.published) } func editPostPostHandler(request: Request, post: BlogPost) throws -> ResponseRepresentable { From 63a55aae07fe5fee9ba0b6c739fdf933c69fd9ac Mon Sep 17 00:00:00 2001 From: Tim Condon Date: Tue, 21 Mar 2017 12:40:59 +0000 Subject: [PATCH 06/21] Don't show drafts on blog index --- Sources/SteamPress/Controllers/BlogController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SteamPress/Controllers/BlogController.swift b/Sources/SteamPress/Controllers/BlogController.swift index 578db7d4..9c7e5da8 100644 --- a/Sources/SteamPress/Controllers/BlogController.swift +++ b/Sources/SteamPress/Controllers/BlogController.swift @@ -42,7 +42,7 @@ struct BlogController { func indexHandler(request: Request) throws -> ResponseRepresentable { let tags = try BlogTag.all() let authors = try BlogUser.all() - let paginatedBlogPosts = try BlogPost.query().sort("created", .descending).paginator(postsPerPage, request: request) + let paginatedBlogPosts = try BlogPost.query().filter("published", true).sort("created", .descending).paginator(postsPerPage, request: request) return try viewFactory.blogIndexView(uri: request.uri, paginatedPosts: paginatedBlogPosts, tags: tags, authors: authors, loggedInUser: getLoggedInUser(in: request), disqusName: getDisqusName(), siteTwitterHandle: getSiteTwitterHandle()) } From fa17e1cb28dbaa56bb84fd24622173c2e4e4b3f5 Mon Sep 17 00:00:00 2001 From: Tim Condon Date: Tue, 21 Mar 2017 12:53:32 +0000 Subject: [PATCH 07/21] Editing a draft and saving it as a draft works correctly and created date is updated accordingly --- .../Controllers/BlogAdminController.swift | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/Sources/SteamPress/Controllers/BlogAdminController.swift b/Sources/SteamPress/Controllers/BlogAdminController.swift index ff18a0fd..9e92ddbb 100644 --- a/Sources/SteamPress/Controllers/BlogAdminController.swift +++ b/Sources/SteamPress/Controllers/BlogAdminController.swift @@ -60,6 +60,10 @@ struct BlogAdminController { let draft = request.data["save-draft"]?.string let publish = request.data["publish"]?.string + if draft == nil && publish == nil { + throw Abort.badRequest + } + // I must be able to inline all of this var tagsArray: [Node] = [] @@ -69,10 +73,6 @@ struct BlogAdminController { else if let tagsStringArray = rawTags as? [String] { tagsArray = tagsStringArray.map { $0.makeNode() } } - - if draft == nil && publish == nil { - throw Abort.badRequest - } if let createPostErrors = validatePostCreation(title: rawTitle, contents: rawContents, slugUrl: rawSlugUrl) { return try viewFactory.createBlogPostView(uri: request.uri, errors: createPostErrors, title: rawTitle, contents: rawContents, slugUrl: rawSlugUrl, tags: tagsArray, isEditing: false, postToEdit: nil, draft: true) @@ -136,6 +136,12 @@ struct BlogAdminController { let rawContents = request.data["inputPostContents"]?.string let rawTags = request.data["inputTags"]?.array let rawSlugUrl = request.data["inputSlugUrl"]?.string + let draft = request.data["save-draft"]?.string + let publish = request.data["publish"]?.string + + if draft == nil && publish == nil { + throw Abort.badRequest + } var tagsArray: [Node] = [] @@ -157,7 +163,6 @@ struct BlogAdminController { var post = post post.title = title post.contents = contents - post.lastEdited = Date() if (post.slugUrl != slugUrl) { post.slugUrl = BlogPost.generateUniqueSlugUrl(from: slugUrl) } @@ -187,6 +192,16 @@ struct BlogAdminController { for newTagString in tagsToAdd { try BlogTag.addTag(newTagString, to: post) } + + if post.published { + post.lastEdited = Date() + } + else { + post.created = Date() + if let _ = publish { + post.published = true + } + } try post.save() From b75e2138e9b6311eda32d549e135e9a20c9287af Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Wed, 22 Mar 2017 12:23:25 +0000 Subject: [PATCH 08/21] Fix typos and issues in README --- README.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c9e382f0..7dc8cc99 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ SteamPress currently supports using [Disqus](https://disqus.com) for the comment } ``` -This will pass it through to the Leaf templates for the Blog index (`blog.leaf`), blog posts (`blogpost.leaf`), author page (`profile.leaf`) and tag page (`tag.leaf`) so you can include it if needs be. If you want to manually set up comments you can do this yourself and just include the necessary files for your provider. This is mainly to provide easily configuration for the [Platform site](https://github.com/brokenhandsio/SteamPressExample). +This will pass it through to the Leaf templates for the Blog index (`blog.leaf`), blog posts (`blogpost.leaf`), author page (`profile.leaf`) and tag page (`tag.leaf`) so you can include it if needs be. If you want to manually set up comments you can do this yourself and just include the necessary files for your provider. This is mainly to provide easy configuration for the [Platform site](https://github.com/brokenhandsio/SteamPressExample). ## Open Graph Twitter Card Support @@ -108,18 +108,18 @@ SteamPress expects there to be a number of Leaf template files in the correct lo The basic structure of your `Resources/View` directory should be: * `blog` - * `blog.leaf` - the main index page - * `blogpost.leaf` - the page for a single blog post - * `tag.leaf` - the page for a tag - * `profile.leaf` - the page for a user profile - * `tags.leaf` - the page for displaying all of the tags - * `authors.leaf` - the page for displaying all of the authors - * `admin` - * `createPost.leaf` - the page for creating and editing a blog post - * `createUser.leaf` - the page for creating and editing a user - * `index.leaf` - the index page for the Admin site - * `login.leaf` - the login page for the Admin site - * `resetPassword.leaf` - the page for resetting your password + * `blog.leaf` - the main index page + * `blogpost.leaf` - the page for a single blog post + * `tag.leaf` - the page for a tag + * `profile.leaf` - the page for a user profile + * `tags.leaf` - the page for displaying all of the tags + * `authors.leaf` - the page for displaying all of the authors + * `admin` + * `createPost.leaf` - the page for creating and editing a blog post + * `createUser.leaf` - the page for creating and editing a user + * `index.leaf` - the index page for the Admin site + * `login.leaf` - the login page for the Admin site + * `resetPassword.leaf` - the page for resetting your password ## Main Blog Site @@ -322,7 +322,6 @@ SteamPress also contains an API for accessing certain things that may be useful. # Known issues * When the admin user is created when first accessing the login screen, sometimes two are created so you need to use the first password displayed. You can then delete the second Admin user in the Admin pane. -* Despite me being a big believer in TDD and it saving me on many occasions, I neglected to actually write any tests for this. So despite the fact that I have been tripped up due to no tests, I haven't written the unit tests yet, mainly because this started out as a Spike to see how easy it would be. They will definitely be coming soon! # Roadmap From 534d36aa5a20c1f29140f5de7f4ec01ace7f352d Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Wed, 22 Mar 2017 12:53:20 +0000 Subject: [PATCH 09/21] Add new parameter to README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7dc8cc99..74a4831c 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,7 @@ The blog post has a number of `Context`s you can pass to the `makeNode()` functi * `.shortSnippet` - this will return the post with an `id`, `title`, `author_name`, `author_username`, `slug_url`, `created_date` (Human readable) and `short_snippet` * `.longSnippet` - this will return the post with an `id`, `title`, `author_name`, `author_username`, `slug_url`, `created_date` (Human readable) and `long_snippet`. It will also include all of the tags in a `tags` object if there are any associated with that post -* `.all` - this returns the post with all information, including both snippet lengths, including author names and human readable dates, as well as both dates in ISO 8601 format under the parameter names `created_date_iso8601` and `last_edited_date_iso8601` +* `.all` - this returns the post with all information, including both snippet lengths, including author names and human readable dates, as well as both dates in ISO 8601 format under the parameter names `created_date_iso8601` and `last_edited_date_iso8601`. If no `Context` is supplied to the `makeNode()` call you will get: @@ -278,6 +278,7 @@ If no `Context` is supplied to the `makeNode()` call you will get: * `bloguser_id` - The ID of the Author of the post * `created` - The time the post was created as a `Double` * `slug_url` +* `published` - Whether the post has been published or not ## Blog User From c12d2703d441ac05524b1a9f392bb0f9143358e5 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Wed, 22 Mar 2017 13:07:11 +0000 Subject: [PATCH 10/21] Add Drafts to features in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 74a4831c..e37edc21 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ There is an example of how it can work in a site (and what it requires in terms * Multiple user accounts * Tags on blog posts * Snippet for posts +* Draft Posts * Works with any Fluent driver * Protected Admin route for creating blog posts * Pagination on the main blog page From e6ae5a3db32be998a56a0c323c683ceaf1d767db Mon Sep 17 00:00:00 2001 From: Tim Condon Date: Wed, 22 Mar 2017 13:20:31 +0000 Subject: [PATCH 11/21] Only show published posts when getting the posts for tags and users --- Sources/SteamPress/Models/BlogTag.swift | 2 +- Sources/SteamPress/Models/BlogUser.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SteamPress/Models/BlogTag.swift b/Sources/SteamPress/Models/BlogTag.swift index d96af153..6fe4df55 100644 --- a/Sources/SteamPress/Models/BlogTag.swift +++ b/Sources/SteamPress/Models/BlogTag.swift @@ -66,7 +66,7 @@ extension BlogTag { extension BlogTag { func blogPosts() throws -> [BlogPost] { - return try siblings().all() + return try siblings().filter("published", true).all() } func deletePivot(for post: BlogPost) throws { diff --git a/Sources/SteamPress/Models/BlogUser.swift b/Sources/SteamPress/Models/BlogUser.swift index 9419dc67..d9046658 100644 --- a/Sources/SteamPress/Models/BlogUser.swift +++ b/Sources/SteamPress/Models/BlogUser.swift @@ -107,7 +107,7 @@ extension BlogUser: Auth.User { extension BlogUser { func posts() throws -> [BlogPost] { - return try children("bloguser_id", BlogPost.self).all() + return try children("bloguser_id", BlogPost.self).filter("published", true).all() } } From 06e477b88474acf0efcc81e472fc1e509bbb049c Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Wed, 22 Mar 2017 20:48:49 +0000 Subject: [PATCH 12/21] Better dependency injection --- .../SteamPress/Controllers/BlogController.swift | 8 +++++--- Sources/SteamPress/Provider.swift | 4 ++-- Sources/SteamPress/Views/LeafViewFactory.swift | 14 +++++++------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Sources/SteamPress/Controllers/BlogController.swift b/Sources/SteamPress/Controllers/BlogController.swift index 9c7e5da8..54ce4f25 100644 --- a/Sources/SteamPress/Controllers/BlogController.swift +++ b/Sources/SteamPress/Controllers/BlogController.swift @@ -14,13 +14,15 @@ struct BlogController { fileprivate let pathCreator: BlogPathCreator fileprivate let viewFactory: ViewFactory fileprivate let postsPerPage: Int + fileprivate let config: Config // MARK: - Initialiser - init(drop: Droplet, pathCreator: BlogPathCreator, viewFactory: ViewFactory, postsPerPage: Int) { + init(drop: Droplet, pathCreator: BlogPathCreator, viewFactory: ViewFactory, postsPerPage: Int, config: Config) { self.drop = drop self.pathCreator = pathCreator self.viewFactory = viewFactory self.postsPerPage = postsPerPage + self.config = config } // MARK: - Add routes @@ -113,11 +115,11 @@ struct BlogController { } private func getDisqusName() -> String? { - return drop.config["disqus", "disqusName"]?.string + return config["disqus", "disqusName"]?.string } private func getSiteTwitterHandle() -> String? { - return drop.config["twitter", "siteHandle"]?.string + return config["twitter", "siteHandle"]?.string } } diff --git a/Sources/SteamPress/Provider.swift b/Sources/SteamPress/Provider.swift index 1d1a6d57..b556c772 100644 --- a/Sources/SteamPress/Provider.swift +++ b/Sources/SteamPress/Provider.swift @@ -17,10 +17,10 @@ public struct Provider: Vapor.Provider { setup(drop) - let viewFactory = LeafViewFactory(drop: drop) + let viewFactory = LeafViewFactory(viewRenderer: drop.view) // Set up the controllers - let blogController = BlogController(drop: drop, pathCreator: pathCreator, viewFactory: viewFactory, postsPerPage: postsPerPage) + let blogController = BlogController(drop: drop, pathCreator: pathCreator, viewFactory: viewFactory, postsPerPage: postsPerPage, config: drop.config) let blogAdminController = BlogAdminController(drop: drop, pathCreator: pathCreator, viewFactory: viewFactory) // Add the routes diff --git a/Sources/SteamPress/Views/LeafViewFactory.swift b/Sources/SteamPress/Views/LeafViewFactory.swift index 654191ac..f7600d9f 100644 --- a/Sources/SteamPress/Views/LeafViewFactory.swift +++ b/Sources/SteamPress/Views/LeafViewFactory.swift @@ -7,7 +7,7 @@ import SwiftSoup struct LeafViewFactory: ViewFactory { - let drop: Droplet + let viewRenderer: ViewRenderer // MARK: - Admin Controller Views @@ -68,7 +68,7 @@ struct LeafViewFactory: ViewFactory { parameters["createBlogPostPage"] = true } - return try drop.view.make("blog/admin/createPost", parameters) + return try viewRenderer.make("blog/admin/createPost", parameters) } func createUserView(editing: Bool = false, errors: [String]? = nil, name: String? = nil, username: String? = nil, passwordError: Bool? = nil, confirmPasswordError: Bool? = nil, resetPasswordRequired: Bool? = nil, userId: Vapor.Node? = nil) throws -> View { @@ -112,7 +112,7 @@ struct LeafViewFactory: ViewFactory { parameters["userId"] = userId } - return try drop.view.make("blog/admin/createUser", parameters) + return try viewRenderer.make("blog/admin/createUser", parameters) } func createLoginView(loginWarning: Bool = false, errors: [String]? = nil, username: String? = nil, password: String? = nil) throws -> View { @@ -136,7 +136,7 @@ struct LeafViewFactory: ViewFactory { parameters["loginWarning"] = true } - return try drop.view.make("blog/admin/login", parameters) + return try viewRenderer.make("blog/admin/login", parameters) } func createBlogAdminView(errors: [String]? = nil) throws -> View { @@ -157,7 +157,7 @@ struct LeafViewFactory: ViewFactory { parameters["blogAdminPage"] = true - return try drop.view.make("blog/admin/index", parameters) + return try viewRenderer.make("blog/admin/index", parameters) } func createResetPasswordView(errors: [String]? = nil, passwordError: Bool? = nil, confirmPasswordError: Bool? = nil) throws -> View { @@ -176,7 +176,7 @@ struct LeafViewFactory: ViewFactory { parameters["confirmPasswordError"] = confirmPasswordError.makeNode() } - return try drop.view.make("blog/admin/resetPassword", parameters) + return try viewRenderer.make("blog/admin/resetPassword", parameters) } func createProfileView(uri: URI, author: BlogUser, isMyProfile: Bool, posts: [BlogPost], loggedInUser: BlogUser?, disqusName: String?, siteTwitterHandle: String?) throws -> View { @@ -295,7 +295,7 @@ struct LeafViewFactory: ViewFactory { viewParameters["site_twitter_handle"] = siteTwitterHandle.makeNode() } - return try drop.view.make(template, viewParameters.makeNode()) + return try viewRenderer.make(template, viewParameters.makeNode()) } } From e931887863291996456b6b8b1c640fb9028cd6f7 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Wed, 22 Mar 2017 21:05:50 +0000 Subject: [PATCH 13/21] Fix the tests from the dependency injection changes --- Tests/SteamPressTests/BlogAdminControllerTests.swift | 2 +- Tests/SteamPressTests/BlogControllerTests.swift | 2 +- Tests/SteamPressTests/LeafViewFactoryTests.swift | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Tests/SteamPressTests/BlogAdminControllerTests.swift b/Tests/SteamPressTests/BlogAdminControllerTests.swift index d41d2dba..a443e960 100644 --- a/Tests/SteamPressTests/BlogAdminControllerTests.swift +++ b/Tests/SteamPressTests/BlogAdminControllerTests.swift @@ -20,7 +20,7 @@ class BlogAdminControllerTests: XCTestCase { let pathCreator = BlogPathCreator(blogPath: nil) // TODO change to Stub let viewFactory = CapturingViewFactory() - let blogController = BlogController(drop: drop, pathCreator: pathCreator, viewFactory: viewFactory, postsPerPage: 5) + let blogController = BlogController(drop: drop, pathCreator: pathCreator, viewFactory: viewFactory, postsPerPage: 5, config: drop.config) blogController.addRoutes() try drop.runCommands() diff --git a/Tests/SteamPressTests/BlogControllerTests.swift b/Tests/SteamPressTests/BlogControllerTests.swift index c74d0caf..fdb8e34e 100644 --- a/Tests/SteamPressTests/BlogControllerTests.swift +++ b/Tests/SteamPressTests/BlogControllerTests.swift @@ -68,7 +68,7 @@ class BlogControllerTests: XCTestCase { viewFactory = CapturingViewFactory() let pathCreator = BlogPathCreator(blogPath: nil) - let blogController = BlogController(drop: drop, pathCreator: pathCreator, viewFactory: viewFactory, postsPerPage: 5) + let blogController = BlogController(drop: drop, pathCreator: pathCreator, viewFactory: viewFactory, postsPerPage: 5, config: config ?? drop.config) blogController.addRoutes() let blogAdminController = BlogAdminController(drop: drop, pathCreator: pathCreator, viewFactory: viewFactory) diff --git a/Tests/SteamPressTests/LeafViewFactoryTests.swift b/Tests/SteamPressTests/LeafViewFactoryTests.swift index 4a5ffcdc..e8a60b68 100644 --- a/Tests/SteamPressTests/LeafViewFactoryTests.swift +++ b/Tests/SteamPressTests/LeafViewFactoryTests.swift @@ -84,11 +84,8 @@ class LeafViewFactoryTests: XCTestCase { // MARK: - Overrides override func setUp() { - let drop = Droplet(arguments: ["dummy/path/", "prepare"], config: nil) viewRenderer = CapturingViewRenderer() - drop.view = viewRenderer - drop.database = database - viewFactory = LeafViewFactory(drop: drop) + viewFactory = LeafViewFactory(viewRenderer: viewRenderer) tagRequest = try! Request(method: .get, uri: tagURI) indexRequest = try! Request(method: .get, uri: indexURI) let printConsole = PrintConsole() From 29ea71ea72f43eeeaf471386151759ec7f4a77bb Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 23 Mar 2017 08:48:40 +0000 Subject: [PATCH 14/21] Pass in both drafts and published posts to the admin page --- Sources/SteamPress/Views/LeafViewFactory.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/SteamPress/Views/LeafViewFactory.swift b/Sources/SteamPress/Views/LeafViewFactory.swift index f7600d9f..f94e904f 100644 --- a/Sources/SteamPress/Views/LeafViewFactory.swift +++ b/Sources/SteamPress/Views/LeafViewFactory.swift @@ -140,15 +140,20 @@ struct LeafViewFactory: ViewFactory { } func createBlogAdminView(errors: [String]? = nil) throws -> View { - let blogPosts = try BlogPost.all() + let publishedBlogPosts = try BlogPost.query().filter("published", true).all() + let draftBlogPosts = try BlogPost.query().filter("published", false).all() let users = try BlogUser.all() var parameters = try Node(node: [ "users": users.makeNode() ]) - if blogPosts.count > 0 { - parameters["posts"] = try blogPosts.makeNode(context: BlogPostContext.all) + if publishedBlogPosts.count > 0 { + parameters["published-posts"] = try publishedBlogPosts.makeNode(context: BlogPostContext.all) + } + + if draftBlogPosts.count > 0 { + parameters["draft-posts"] = try draftBlogPosts.makeNode(context: BlogPostContext.all) } if let errors = errors { From 0238a56f4365f28d88bbbe10078501114db0d23b Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 23 Mar 2017 12:23:15 +0000 Subject: [PATCH 15/21] Less type inference for BlogPost makeNode as it is starting to get big --- Sources/SteamPress/Models/BlogPost.swift | 57 +++++++++++------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/Sources/SteamPress/Models/BlogPost.swift b/Sources/SteamPress/Models/BlogPost.swift index 227f8a51..65dc9fd6 100644 --- a/Sources/SteamPress/Models/BlogPost.swift +++ b/Sources/SteamPress/Models/BlogPost.swift @@ -49,15 +49,14 @@ extension BlogPost: NodeRepresentable { public func makeNode(context: Context) throws -> Node { let createdTime = created.timeIntervalSince1970 - var node = try Node(node: [ - "id": id, - "title": title, - "contents": contents, - "bloguser_id": author, - "created": createdTime, - "slug_url": slugUrl, - "published": published, - ]) + var node: [String: Node] = [:] + node["id"] = id + node["title"] = title.makeNode() + node["contents"] = contents.makeNode() + node["bloguser_id"] = author?.makeNode() + node["created"] = createdTime.makeNode() + node["slug_url"] = slugUrl.makeNode() + node["published"] = published.makeNode() if let lastEdited = lastEdited { node["last_edited"] = lastEdited.timeIntervalSince1970.makeNode() @@ -71,27 +70,25 @@ extension BlogPost: NodeRepresentable { switch context { case BlogPostContext.shortSnippet: - node = try Node(node: [ - "id": id, - "title": title, - "author_name": try getAuthor()?.name.makeNode(), - "author_username": try getAuthor()?.username.makeNode(), - "short_snippet": shortSnippet().makeNode(), - "created_date": createdDate.makeNode(), - "published": published, - "slug_url": slugUrl - ]) + node = [:] + node["id"] = id + node["title"] = title.makeNode() + node["author_name"] = try getAuthor()?.name.makeNode() + node["author_username"] = try getAuthor()?.username.makeNode() + node["short_snippet"] = shortSnippet().makeNode() + node["created_date"] = createdDate.makeNode() + node["slug_url"] = slugUrl.makeNode() + node["published"] = published.makeNode() case BlogPostContext.longSnippet: - node = try Node(node: [ - "id": id, - "title": title, - "author_name": try getAuthor()?.name.makeNode(), - "author_username": try getAuthor()?.username.makeNode(), - "long_snippet": longSnippet().makeNode(), - "created_date": createdDate.makeNode(), - "published": published, - "slug_url": slugUrl - ]) + node = [:] + node["id"] = id + node["title"] = title.makeNode() + node["author_name"] = try getAuthor()?.name.makeNode() + node["author_username"] = try getAuthor()?.username.makeNode() + node["long_snippet"] = longSnippet().makeNode() + node["created_date"] = createdDate.makeNode() + node["slug_url"] = slugUrl.makeNode() + node["published"] = published.makeNode() let allTags = try tags() @@ -129,7 +126,7 @@ extension BlogPost: NodeRepresentable { default: break } - return node + return try node.makeNode() } } From fb718ed65a0409f9975aa4da19050a3df3e57a53 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 23 Mar 2017 12:36:34 +0000 Subject: [PATCH 16/21] Fix the tests and add tests for draft posts in Admin view --- Tests/SteamPressTests/Helpers/TestDataBuilder.swift | 4 ++-- Tests/SteamPressTests/LeafViewFactoryTests.swift | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Tests/SteamPressTests/Helpers/TestDataBuilder.swift b/Tests/SteamPressTests/Helpers/TestDataBuilder.swift index c1a771b4..0fa63a57 100644 --- a/Tests/SteamPressTests/Helpers/TestDataBuilder.swift +++ b/Tests/SteamPressTests/Helpers/TestDataBuilder.swift @@ -9,8 +9,8 @@ struct TestDataBuilder { return BlogUser(name: name, username: "timc", password: "password") } - static func anyPost(slugUrl: String = "some-exciting-title", author: BlogUser = TestDataBuilder.anyUser(), creationDate: Date = Date()) -> BlogPost { - return BlogPost(title: "An Exciting Post!", contents: "

This is a blog post

", author: author, creationDate: creationDate, slugUrl: slugUrl, published: true) + static func anyPost(title: String = "An Exciting Post!", slugUrl: String = "some-exciting-title", author: BlogUser = TestDataBuilder.anyUser(), creationDate: Date = Date(), published: Bool = true) -> BlogPost { + return BlogPost(title: title, contents: "

This is a blog post

", author: author, creationDate: creationDate, slugUrl: slugUrl, published: published) } static func anyPostWithImage(author: BlogUser = TestDataBuilder.anyUser()) -> BlogPost { diff --git a/Tests/SteamPressTests/LeafViewFactoryTests.swift b/Tests/SteamPressTests/LeafViewFactoryTests.swift index e8a60b68..8f71f95f 100644 --- a/Tests/SteamPressTests/LeafViewFactoryTests.swift +++ b/Tests/SteamPressTests/LeafViewFactoryTests.swift @@ -451,13 +451,17 @@ class LeafViewFactoryTests: XCTestCase { func testBlogAdminViewGetsCorrectParameters() throws { // Add some stuff to the database let (posts, _, users) = try setupBlogIndex() + var draftPost = TestDataBuilder.anyPost(title: "[DRAFT] This will be awesome", author: users.first!, published: false) + try draftPost.save() let _ = try viewFactory.createBlogAdminView() XCTAssertNil(viewRenderer.capturedContext?["errors"]) XCTAssertTrue((viewRenderer.capturedContext?["blogAdminPage"]?.bool) ?? false) XCTAssertEqual(viewRenderer.capturedContext?["users"]?.nodeArray?.count, 2) XCTAssertEqual(viewRenderer.capturedContext?["users"]?.nodeArray?.first?["name"]?.string, users.first?.name) - XCTAssertEqual(viewRenderer.capturedContext?["posts"]?.nodeArray?.count, 2) - XCTAssertEqual(viewRenderer.capturedContext?["posts"]?.nodeArray?.first?["title"]?.string, posts.first?.title) + XCTAssertEqual(viewRenderer.capturedContext?["published-posts"]?.nodeArray?.count, 2) + XCTAssertEqual(viewRenderer.capturedContext?["published-posts"]?.nodeArray?.first?["title"]?.string, posts.first?.title) + XCTAssertEqual(viewRenderer.capturedContext?["draft-posts"]?.nodeArray?.count, 1) + XCTAssertEqual(viewRenderer.capturedContext?["draft-posts"]?.nodeArray?.first?["title"]?.string, draftPost.title) XCTAssertEqual(viewRenderer.leafPath, "blog/admin/index") } From fe91fa63348b1a43d048043652e1dff88f177126 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 23 Mar 2017 12:40:50 +0000 Subject: [PATCH 17/21] Sort admin page posts in descending date order --- Sources/SteamPress/Views/LeafViewFactory.swift | 4 ++-- Tests/SteamPressTests/LeafViewFactoryTests.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SteamPress/Views/LeafViewFactory.swift b/Sources/SteamPress/Views/LeafViewFactory.swift index f94e904f..4217ff7e 100644 --- a/Sources/SteamPress/Views/LeafViewFactory.swift +++ b/Sources/SteamPress/Views/LeafViewFactory.swift @@ -140,8 +140,8 @@ struct LeafViewFactory: ViewFactory { } func createBlogAdminView(errors: [String]? = nil) throws -> View { - let publishedBlogPosts = try BlogPost.query().filter("published", true).all() - let draftBlogPosts = try BlogPost.query().filter("published", false).all() + let publishedBlogPosts = try BlogPost.query().filter("published", true).sort("created", .descending).all() + let draftBlogPosts = try BlogPost.query().filter("published", false).sort("created", .descending).all() let users = try BlogUser.all() var parameters = try Node(node: [ diff --git a/Tests/SteamPressTests/LeafViewFactoryTests.swift b/Tests/SteamPressTests/LeafViewFactoryTests.swift index 8f71f95f..e6237902 100644 --- a/Tests/SteamPressTests/LeafViewFactoryTests.swift +++ b/Tests/SteamPressTests/LeafViewFactoryTests.swift @@ -459,7 +459,7 @@ class LeafViewFactoryTests: XCTestCase { XCTAssertEqual(viewRenderer.capturedContext?["users"]?.nodeArray?.count, 2) XCTAssertEqual(viewRenderer.capturedContext?["users"]?.nodeArray?.first?["name"]?.string, users.first?.name) XCTAssertEqual(viewRenderer.capturedContext?["published-posts"]?.nodeArray?.count, 2) - XCTAssertEqual(viewRenderer.capturedContext?["published-posts"]?.nodeArray?.first?["title"]?.string, posts.first?.title) + XCTAssertEqual(viewRenderer.capturedContext?["published-posts"]?.nodeArray?.first?["title"]?.string, posts[1].title) XCTAssertEqual(viewRenderer.capturedContext?["draft-posts"]?.nodeArray?.count, 1) XCTAssertEqual(viewRenderer.capturedContext?["draft-posts"]?.nodeArray?.first?["title"]?.string, draftPost.title) XCTAssertEqual(viewRenderer.leafPath, "blog/admin/index") From 4f46294ca6e1093d5f034ffb13ab29a1b71dffa4 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 23 Mar 2017 12:54:53 +0000 Subject: [PATCH 18/21] Sort tag page posts by descending order and make sure we don't get drafts --- .../SteamPress/Controllers/BlogController.swift | 2 +- Sources/SteamPress/Models/BlogTag.swift | 2 +- Sources/SteamPress/Models/BlogUser.swift | 2 +- Tests/SteamPressTests/BlogControllerTests.swift | 15 +++++++++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Sources/SteamPress/Controllers/BlogController.swift b/Sources/SteamPress/Controllers/BlogController.swift index 54ce4f25..7cd56c0a 100644 --- a/Sources/SteamPress/Controllers/BlogController.swift +++ b/Sources/SteamPress/Controllers/BlogController.swift @@ -74,7 +74,7 @@ struct BlogController { throw Abort.notFound } - let paginatedBlogPosts = try tag.blogPosts().sorted { $0.created > $1.created }.paginator(postsPerPage, request: request) + let paginatedBlogPosts = try tag.blogPosts().paginator(postsPerPage, request: request) return try viewFactory.tagView(uri: request.uri, tag: tag, paginatedPosts: paginatedBlogPosts, user: getLoggedInUser(in: request), disqusName: getDisqusName(), siteTwitterHandle: getSiteTwitterHandle()) } diff --git a/Sources/SteamPress/Models/BlogTag.swift b/Sources/SteamPress/Models/BlogTag.swift index 6fe4df55..46d05e17 100644 --- a/Sources/SteamPress/Models/BlogTag.swift +++ b/Sources/SteamPress/Models/BlogTag.swift @@ -66,7 +66,7 @@ extension BlogTag { extension BlogTag { func blogPosts() throws -> [BlogPost] { - return try siblings().filter("published", true).all() + return try siblings().filter("published", true).sort("created", .descending).all() } func deletePivot(for post: BlogPost) throws { diff --git a/Sources/SteamPress/Models/BlogUser.swift b/Sources/SteamPress/Models/BlogUser.swift index d9046658..5bed530b 100644 --- a/Sources/SteamPress/Models/BlogUser.swift +++ b/Sources/SteamPress/Models/BlogUser.swift @@ -107,7 +107,7 @@ extension BlogUser: Auth.User { extension BlogUser { func posts() throws -> [BlogPost] { - return try children("bloguser_id", BlogPost.self).filter("published", true).all() + return try children("bloguser_id", BlogPost.self).sort("created", .descending).filter("published", true).all() } } diff --git a/Tests/SteamPressTests/BlogControllerTests.swift b/Tests/SteamPressTests/BlogControllerTests.swift index fdb8e34e..258a1deb 100644 --- a/Tests/SteamPressTests/BlogControllerTests.swift +++ b/Tests/SteamPressTests/BlogControllerTests.swift @@ -31,6 +31,7 @@ class BlogControllerTests: XCTestCase { ("testAllTagsPageGetsTwitterHandleIfSet", testAllTagsPageGetsTwitterHandleIfSet), ("testAllTagsPageGetsAllTags", testAllTagsPageGetsAllTags), ("testAllAuthorsPageGetAllAuthors", testAllAuthorsPageGetAllAuthors), + ("testTagPageGetsOnlyPublishedPostsInDescendingOrder", testTagPageGetsOnlyPublishedPostsInDescendingOrder), ] private var drop: Droplet! @@ -368,6 +369,20 @@ class BlogControllerTests: XCTestCase { XCTAssertEqual("Luke", viewFactory.allAuthorsPageAuthors?.first?.name) } + func testTagPageGetsOnlyPublishedPostsInDescendingOrder() throws { + try setupDrop() + var post2 = TestDataBuilder.anyPost(title: "A later post", author: self.user) + try post2.save() + var draftPost = TestDataBuilder.anyPost(author: self.user, published: false) + try draftPost.save() + try BlogTag.addTag("tatooine", to: post2) + try BlogTag.addTag("tatooine", to: draftPost) + _ = try drop.respond(to: tagRequest) + + XCTAssertEqual(2, viewFactory.tagPosts?.total) + XCTAssertEqual(post2.title, viewFactory.tagPosts?.data?.first?.title) + } + } import URI From e970a2f05c879fbfc1663697c95fe4f1c1b99f25 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 23 Mar 2017 12:56:51 +0000 Subject: [PATCH 19/21] Test to ensure author view posts only contain published posts and are in descending date order --- Tests/SteamPressTests/BlogControllerTests.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tests/SteamPressTests/BlogControllerTests.swift b/Tests/SteamPressTests/BlogControllerTests.swift index 258a1deb..d4e37947 100644 --- a/Tests/SteamPressTests/BlogControllerTests.swift +++ b/Tests/SteamPressTests/BlogControllerTests.swift @@ -32,6 +32,7 @@ class BlogControllerTests: XCTestCase { ("testAllTagsPageGetsAllTags", testAllTagsPageGetsAllTags), ("testAllAuthorsPageGetAllAuthors", testAllAuthorsPageGetAllAuthors), ("testTagPageGetsOnlyPublishedPostsInDescendingOrder", testTagPageGetsOnlyPublishedPostsInDescendingOrder), + ("testAuthorPageGetsOnlyPublishedPostsInDescendingOrder", testAuthorPageGetsOnlyPublishedPostsInDescendingOrder), ] private var drop: Droplet! @@ -383,6 +384,18 @@ class BlogControllerTests: XCTestCase { XCTAssertEqual(post2.title, viewFactory.tagPosts?.data?.first?.title) } + func testAuthorPageGetsOnlyPublishedPostsInDescendingOrder() throws { + try setupDrop() + var post2 = TestDataBuilder.anyPost(title: "A later post", author: self.user) + try post2.save() + var draftPost = TestDataBuilder.anyPost(author: self.user, published: false) + try draftPost.save() + _ = try drop.respond(to: authorRequest) + + XCTAssertEqual(2, viewFactory.authorPosts?.count) + XCTAssertEqual(post2.title, viewFactory.authorPosts?.first?.title) + } + } import URI From 9be0999dd82019bed5569f5a88c83de4ff43c050 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 23 Mar 2017 23:24:28 +0000 Subject: [PATCH 20/21] Keep the naming consistent with - and _ --- Sources/SteamPress/Views/LeafViewFactory.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SteamPress/Views/LeafViewFactory.swift b/Sources/SteamPress/Views/LeafViewFactory.swift index 4217ff7e..88e05d6e 100644 --- a/Sources/SteamPress/Views/LeafViewFactory.swift +++ b/Sources/SteamPress/Views/LeafViewFactory.swift @@ -149,11 +149,11 @@ struct LeafViewFactory: ViewFactory { ]) if publishedBlogPosts.count > 0 { - parameters["published-posts"] = try publishedBlogPosts.makeNode(context: BlogPostContext.all) + parameters["published_posts"] = try publishedBlogPosts.makeNode(context: BlogPostContext.all) } if draftBlogPosts.count > 0 { - parameters["draft-posts"] = try draftBlogPosts.makeNode(context: BlogPostContext.all) + parameters["draft_posts"] = try draftBlogPosts.makeNode(context: BlogPostContext.all) } if let errors = errors { From 27ad958a73fa6aca99e84144a5beb9d222df4672 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 24 Mar 2017 00:03:59 +0000 Subject: [PATCH 21/21] Test around published parameter on BlogPost --- .../SteamPressTests/LeafViewFactoryTests.swift | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Tests/SteamPressTests/LeafViewFactoryTests.swift b/Tests/SteamPressTests/LeafViewFactoryTests.swift index e6237902..9aea684b 100644 --- a/Tests/SteamPressTests/LeafViewFactoryTests.swift +++ b/Tests/SteamPressTests/LeafViewFactoryTests.swift @@ -63,6 +63,7 @@ class LeafViewFactoryTests: XCTestCase { ("testCreateBlogPostViewWhenEditing", testCreateBlogPostViewWhenEditing), ("testEditBlogPostViewThrowsWithNoPostToEdit", testEditBlogPostViewThrowsWithNoPostToEdit), ("testCreateBlogPostViewWithErrorsAndNoTitleOrContentsSupplied", testCreateBlogPostViewWithErrorsAndNoTitleOrContentsSupplied), + ("testDraftPassedThroughWhenEditingABlogPostThatHasNotBeenPublished", testDraftPassedThroughWhenEditingABlogPostThatHasNotBeenPublished) ] // MARK: - Properties @@ -458,10 +459,10 @@ class LeafViewFactoryTests: XCTestCase { XCTAssertTrue((viewRenderer.capturedContext?["blogAdminPage"]?.bool) ?? false) XCTAssertEqual(viewRenderer.capturedContext?["users"]?.nodeArray?.count, 2) XCTAssertEqual(viewRenderer.capturedContext?["users"]?.nodeArray?.first?["name"]?.string, users.first?.name) - XCTAssertEqual(viewRenderer.capturedContext?["published-posts"]?.nodeArray?.count, 2) - XCTAssertEqual(viewRenderer.capturedContext?["published-posts"]?.nodeArray?.first?["title"]?.string, posts[1].title) - XCTAssertEqual(viewRenderer.capturedContext?["draft-posts"]?.nodeArray?.count, 1) - XCTAssertEqual(viewRenderer.capturedContext?["draft-posts"]?.nodeArray?.first?["title"]?.string, draftPost.title) + XCTAssertEqual(viewRenderer.capturedContext?["published_posts"]?.nodeArray?.count, 2) + XCTAssertEqual(viewRenderer.capturedContext?["published_posts"]?.nodeArray?.first?["title"]?.string, posts[1].title) + XCTAssertEqual(viewRenderer.capturedContext?["draft_posts"]?.nodeArray?.count, 1) + XCTAssertEqual(viewRenderer.capturedContext?["draft_posts"]?.nodeArray?.first?["title"]?.string, draftPost.title) XCTAssertEqual(viewRenderer.leafPath, "blog/admin/index") } @@ -561,6 +562,7 @@ class LeafViewFactoryTests: XCTestCase { XCTAssertTrue((viewRenderer.capturedContext?["editing"]?.bool) ?? false) XCTAssertEqual(viewRenderer.capturedContext?["post"]?["title"]?.string, postToEdit.title) XCTAssertNil(viewRenderer.capturedContext?["createBlogPostPage"]) + XCTAssertEqual(viewRenderer.capturedContext?["post"]?["published"]?.bool, true) } func testEditBlogPostViewThrowsWithNoPostToEdit() throws { @@ -583,6 +585,13 @@ class LeafViewFactoryTests: XCTestCase { XCTAssertEqual(viewRenderer.capturedContext?["errors"]?.nodeArray?.first?.string, expectedError) } + func testDraftPassedThroughWhenEditingABlogPostThatHasNotBeenPublished() throws { + let postToEdit = TestDataBuilder.anyPost(published: false) + let _ = try viewFactory.createBlogPostView(uri: editPostURI, title: postToEdit.title, contents: postToEdit.contents, slugUrl: postToEdit.slugUrl, tags: ["test".makeNode()], isEditing: true, postToEdit: postToEdit) + XCTAssertEqual(viewRenderer.capturedContext?["post"]?["published"]?.bool, false) + } + + // MARK: - Helpers private func setupBlogIndex() throws -> ([BlogPost], [BlogTag], [BlogUser]) {