From 5c1d0634f2d2c4760dcccffc23e428dcce0f9fa2 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 26 Mar 2020 15:26:21 +0000 Subject: [PATCH 01/70] Update manifest to 5.2 --- Package.swift | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Package.swift b/Package.swift index 3a41bf30..827ae667 100644 --- a/Package.swift +++ b/Package.swift @@ -1,20 +1,28 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.2 import PackageDescription let package = Package( name: "SteamPress", + platforms: [ + .macOS(.v10_15) + ], products: [ .library(name: "SteamPress", targets: ["SteamPress"]), ], dependencies: [ - .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), + .package(name: "Vapor", url: "https://github.com/vapor/vapor.git", from: "3.0.0"), .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.0.0"), - .package(url: "https://github.com/vapor-community/markdown.git", from: "0.4.0"), - .package(url: "https://github.com/vapor/auth.git", from: "2.0.0"), + .package(name: "SwiftMarkdown", url: "https://github.com/vapor-community/markdown.git", from: "0.4.0"), + .package(name: "Auth", url: "https://github.com/vapor/auth.git", from: "2.0.0"), ], targets: [ - .target(name: "SteamPress", dependencies: ["Vapor", "SwiftSoup", "SwiftMarkdown", "Authentication"]), + .target(name: "SteamPress", dependencies: [ + "Vapor", + "SwiftSoup", + "SwiftMarkdown", + .product(name: "Authentication", package: "Auth") + ]), .testTarget(name: "SteamPressTests", dependencies: ["SteamPress"]), ] ) From 2dbd465ed6949ba5ec1d7491de697df9add2902a Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 26 Mar 2020 15:39:37 +0000 Subject: [PATCH 02/70] Upgrade to Vapor 4 --- .github/workflows/ci.yml | 4 ++-- Package.swift | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca6ec5f9..fb84d1f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,14 +4,14 @@ on: jobs: xenial: container: - image: vapor/swift:5.1-xenial + image: vapor/swift:5.2-xenial runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - run: swift test --enable-test-discovery --enable-code-coverage bionic: container: - image: vapor/swift:5.1-bionic + image: vapor/swift:5.2-bionic runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 diff --git a/Package.swift b/Package.swift index 827ae667..27d13f86 100644 --- a/Package.swift +++ b/Package.swift @@ -11,17 +11,16 @@ let package = Package( .library(name: "SteamPress", targets: ["SteamPress"]), ], dependencies: [ - .package(name: "Vapor", url: "https://github.com/vapor/vapor.git", from: "3.0.0"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-rc"), .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.0.0"), - .package(name: "SwiftMarkdown", url: "https://github.com/vapor-community/markdown.git", from: "0.4.0"), - .package(name: "Auth", url: "https://github.com/vapor/auth.git", from: "2.0.0"), + .package(name: "SwiftMarkdown", url: "https://github.com/vapor-community/markdown.git", from: "0.6.1"), ], targets: [ .target(name: "SteamPress", dependencies: [ - "Vapor", + .product(name: "Vapor", package: "vapor"), "SwiftSoup", "SwiftMarkdown", - .product(name: "Authentication", package: "Auth") +// .product(name: "Authentication", package: "vapor") ]), .testTarget(name: "SteamPressTests", dependencies: ["SteamPress"]), ] From 64d75c6a9184f36725819407651ab89746b1cc52 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 26 Mar 2020 15:44:06 +0000 Subject: [PATCH 03/70] Update RouteCollection protocol conformances --- .../Controllers/API/APIController.swift | 4 ++-- .../Controllers/API/APITagController.swift | 4 ++-- .../Controllers/Admin/LoginController.swift | 8 ++++---- .../Admin/PostAdminController.swift | 12 ++++++------ .../Admin/UserAdminController.swift | 12 ++++++------ .../Controllers/BlogAdminController.swift | 4 ++-- .../Controllers/BlogController.swift | 18 +++++++++--------- .../Controllers/FeedController.swift | 6 +++--- 8 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Sources/SteamPress/Controllers/API/APIController.swift b/Sources/SteamPress/Controllers/API/APIController.swift index 4d2147d9..7d9f53b1 100644 --- a/Sources/SteamPress/Controllers/API/APIController.swift +++ b/Sources/SteamPress/Controllers/API/APIController.swift @@ -1,8 +1,8 @@ import Vapor struct APIController: RouteCollection { - func boot(router: Router) throws { - let apiRoutes = router.grouped("api") + func boot(routes: RoutesBuilder) throws { + let apiRoutes = routes.grouped("api") let apiTagController = APITagController() try apiRoutes.register(collection: apiTagController) diff --git a/Sources/SteamPress/Controllers/API/APITagController.swift b/Sources/SteamPress/Controllers/API/APITagController.swift index 9c39b813..c1155aec 100644 --- a/Sources/SteamPress/Controllers/API/APITagController.swift +++ b/Sources/SteamPress/Controllers/API/APITagController.swift @@ -1,8 +1,8 @@ import Vapor struct APITagController: RouteCollection { - func boot(router: Router) throws { - let tagsRoute = router.grouped("tags") + func boot(routes: RoutesBuilder) throws { + let tagsRoute = routes.grouped("tags") tagsRoute.get(use: allTagsHandler) } diff --git a/Sources/SteamPress/Controllers/Admin/LoginController.swift b/Sources/SteamPress/Controllers/Admin/LoginController.swift index aa50c1d8..dee1f47b 100644 --- a/Sources/SteamPress/Controllers/Admin/LoginController.swift +++ b/Sources/SteamPress/Controllers/Admin/LoginController.swift @@ -12,12 +12,12 @@ struct LoginController: RouteCollection { } // MARK: - Route setup - func boot(router: Router) throws { - router.get("login", use: loginHandler) - router.post("login", use: loginPostHandler) + func boot(routes: RoutesBuilder) throws { + routes.get("login", use: loginHandler) + routes.post("login", use: loginPostHandler) let redirectMiddleware = BlogLoginRedirectAuthMiddleware(pathCreator: pathCreator) - let protectedRoutes = router.grouped(redirectMiddleware) + let protectedRoutes = routes.grouped(redirectMiddleware) protectedRoutes.post("logout", use: logoutHandler) protectedRoutes.get("resetPassword", use: resetPasswordHandler) protectedRoutes.post("resetPassword", use: resetPasswordPostHandler) diff --git a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift index 53d724e4..ecd75c19 100644 --- a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift @@ -11,12 +11,12 @@ struct PostAdminController: RouteCollection { } // MARK: - Route setup - func boot(router: Router) throws { - router.get("createPost", use: createPostHandler) - router.post("createPost", use: createPostPostHandler) - router.get("posts", BlogPost.parameter, "edit", use: editPostHandler) - router.post("posts", BlogPost.parameter, "edit", use: editPostPostHandler) - router.post("posts", BlogPost.parameter, "delete", use: deletePostHandler) + func boot(routes: RoutesBuilder) throws { + routes.get("createPost", use: createPostHandler) + routes.post("createPost", use: createPostPostHandler) + routes.get("posts", BlogPost.parameter, "edit", use: editPostHandler) + routes.post("posts", BlogPost.parameter, "edit", use: editPostPostHandler) + routes.post("posts", BlogPost.parameter, "delete", use: deletePostHandler) } // MARK: - Route handlers diff --git a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift index 39601db4..4c83573a 100644 --- a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift @@ -12,12 +12,12 @@ struct UserAdminController: RouteCollection { } // MARK: - Route setup - func boot(router: Router) throws { - router.get("createUser", use: createUserHandler) - router.post("createUser", use: createUserPostHandler) - router.get("users", BlogUser.parameter, "edit", use: editUserHandler) - router.post("users", BlogUser.parameter, "edit", use: editUserPostHandler) - router.post("users", BlogUser.parameter, "delete", use: deleteUserPostHandler) + func boot(routes: RoutesBuilder) throws { + routes.get("createUser", use: createUserHandler) + routes.post("createUser", use: createUserPostHandler) + routes.get("users", BlogUser.parameter, "edit", use: editUserHandler) + routes.post("users", BlogUser.parameter, "edit", use: editUserPostHandler) + routes.post("users", BlogUser.parameter, "delete", use: deleteUserPostHandler) } // MARK: - Route handlers diff --git a/Sources/SteamPress/Controllers/BlogAdminController.swift b/Sources/SteamPress/Controllers/BlogAdminController.swift index 805c617d..d678f175 100644 --- a/Sources/SteamPress/Controllers/BlogAdminController.swift +++ b/Sources/SteamPress/Controllers/BlogAdminController.swift @@ -12,8 +12,8 @@ struct BlogAdminController: RouteCollection { } // MARK: - Route setup - func boot(router: Router) throws { - let adminRoutes = router.grouped("admin") + func boot(routes: RoutesBuilder) throws { + let adminRoutes = routes.grouped("admin") let redirectMiddleware = BlogLoginRedirectAuthMiddleware(pathCreator: pathCreator) let adminProtectedRoutes = adminRoutes.grouped(redirectMiddleware) diff --git a/Sources/SteamPress/Controllers/BlogController.swift b/Sources/SteamPress/Controllers/BlogController.swift index 19b54213..1c7be230 100644 --- a/Sources/SteamPress/Controllers/BlogController.swift +++ b/Sources/SteamPress/Controllers/BlogController.swift @@ -22,18 +22,18 @@ struct BlogController: RouteCollection { } // MARK: - Add routes - func boot(router: Router) throws { - router.get(use: indexHandler) - router.get(blogPostsPath, String.parameter, use: blogPostHandler) - router.get(blogPostsPath, use: blogPostIndexRedirectHandler) - router.get(searchPath, use: searchHandler) + func boot(routes: RoutesBuilder) throws { + routes.get(use: indexHandler) + routes.get(blogPostsPath, String.parameter, use: blogPostHandler) + routes.get(blogPostsPath, use: blogPostIndexRedirectHandler) + routes.get(searchPath, use: searchHandler) if enableAuthorPages { - router.get(authorsPath, use: allAuthorsViewHandler) - router.get(authorsPath, String.parameter, use: authorViewHandler) + routes.get(authorsPath, use: allAuthorsViewHandler) + routes.get(authorsPath, String.parameter, use: authorViewHandler) } if enableTagsPages { - router.get(tagsPath, BlogTag.parameter, use: tagViewHandler) - router.get(tagsPath, use: allTagsViewHandler) + routes.get(tagsPath, BlogTag.parameter, use: tagViewHandler) + routes.get(tagsPath, use: allTagsViewHandler) } } diff --git a/Sources/SteamPress/Controllers/FeedController.swift b/Sources/SteamPress/Controllers/FeedController.swift index b51b03de..a3ee6248 100644 --- a/Sources/SteamPress/Controllers/FeedController.swift +++ b/Sources/SteamPress/Controllers/FeedController.swift @@ -28,8 +28,8 @@ struct FeedController: RouteCollection { } // MARK: - Route Collection - func boot(router: Router) throws { - router.get("atom.xml", use: atomGenerator.feedHandler) - router.get("rss.xml", use: rssGenerator.feedHandler) + func boot(routes: RoutesBuilder) throws { + routes.get("atom.xml", use: atomGenerator.feedHandler) + routes.get("rss.xml", use: rssGenerator.feedHandler) } } From e2871ac36481bffaa44b4e94f884a1c81fd3508f Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 26 Mar 2020 15:45:14 +0000 Subject: [PATCH 04/70] Rename syncDecode to decode --- Sources/SteamPress/Controllers/Admin/LoginController.swift | 4 ++-- .../SteamPress/Controllers/Admin/PostAdminController.swift | 4 ++-- .../SteamPress/Controllers/Admin/UserAdminController.swift | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/SteamPress/Controllers/Admin/LoginController.swift b/Sources/SteamPress/Controllers/Admin/LoginController.swift index dee1f47b..f5e12ba1 100644 --- a/Sources/SteamPress/Controllers/Admin/LoginController.swift +++ b/Sources/SteamPress/Controllers/Admin/LoginController.swift @@ -31,7 +31,7 @@ struct LoginController: RouteCollection { } func loginPostHandler(_ req: Request) throws -> EventLoopFuture { - let loginData = try req.content.syncDecode(LoginData.self) + let loginData = try req.content.decode(LoginData.self) var loginErrors = [String]() var usernameError = false var passwordError = false @@ -85,7 +85,7 @@ struct LoginController: RouteCollection { } func resetPasswordPostHandler(_ req: Request) throws -> EventLoopFuture { - let data = try req.content.syncDecode(ResetPasswordData.self) + let data = try req.content.decode(ResetPasswordData.self) var resetPasswordErrors = [String]() var passwordError: Bool? diff --git a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift index ecd75c19..1478ef19 100644 --- a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift @@ -26,7 +26,7 @@ struct PostAdminController: RouteCollection { } func createPostPostHandler(_ req: Request) throws -> EventLoopFuture { - let data = try req.content.syncDecode(CreatePostData.self) + let data = try req.content.decode(CreatePostData.self) let author = try req.requireAuthenticated(BlogUser.self) if data.draft == nil && data.publish == nil { @@ -103,7 +103,7 @@ struct PostAdminController: RouteCollection { } func editPostPostHandler(_ req: Request) throws -> EventLoopFuture { - let data = try req.content.syncDecode(CreatePostData.self) + let data = try req.content.decode(CreatePostData.self) return try req.parameters.next(BlogPost.self).flatMap { post in if let errors = self.validatePostCreation(data) { let presenter = try req.make(BlogAdminPresenter.self) diff --git a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift index 4c83573a..1aa9c24c 100644 --- a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift @@ -27,7 +27,7 @@ struct UserAdminController: RouteCollection { } func createUserPostHandler(_ req: Request) throws -> EventLoopFuture { - let data = try req.content.syncDecode(CreateUserData.self) + let data = try req.content.decode(CreateUserData.self) return try validateUserCreation(data, on: req).flatMap { createUserErrors in if let errors = createUserErrors { @@ -67,7 +67,7 @@ struct UserAdminController: RouteCollection { func editUserPostHandler(_ req: Request) throws -> EventLoopFuture { return try req.parameters.next(BlogUser.self).flatMap { user in - let data = try req.content.syncDecode(CreateUserData.self) + let data = try req.content.decode(CreateUserData.self) guard let name = data.name, let username = data.username else { throw Abort(.internalServerError) From a9f0f7023a02c881db402559d843844edf56d714 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 26 Mar 2020 15:50:08 +0000 Subject: [PATCH 05/70] Update URL accessors --- .../Extensions/URL+Converters.swift | 37 +++---------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/Sources/SteamPress/Extensions/URL+Converters.swift b/Sources/SteamPress/Extensions/URL+Converters.swift index 889960ef..49f91b62 100644 --- a/Sources/SteamPress/Extensions/URL+Converters.swift +++ b/Sources/SteamPress/Extensions/URL+Converters.swift @@ -3,46 +3,19 @@ import Vapor extension Request { func url() throws -> URL { - let path = self.http.url.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" - - let hostname: String - if let envURL = Environment.get("WEBSITE_URL") { - hostname = envURL - } else { - hostname = self.http.remotePeer.description - } - - let urlString = "\(hostname)\(path)" - guard let url = URL(string: urlString) else { - throw SteamPressError(identifier: "SteamPressError", "Failed to convert url path to URL") - } - return url + let path = self.url.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + let rootURL = try self.rootUrl() + return rootURL.appendingPathComponent(path) } func rootUrl() throws -> URL { - if let envURL = Environment.get("WEBSITE_URL") { - guard let url = URL(string: envURL) else { - throw SteamPressError(identifier: "SteamPressError", "Failed to convert url hostname to URL") - } - return url + guard let hostname = Environment.get("WEBSITE_URL") else { + throw SteamPressError(identifier: "SteamPressError", "WEBSITE_URL not set") } - var hostname = self.http.remotePeer.description - if hostname == "" { - hostname = "/" - } guard let url = URL(string: hostname) else { throw SteamPressError(identifier: "SteamPressError", "Failed to convert url hostname to URL") } return url } } - -private extension String { - func replacingFirstOccurrence(of target: String, with replaceString: String) -> String { - if let range = self.range(of: target) { - return self.replacingCharacters(in: range, with: replaceString) - } - return self - } -} From 07900edaf0e920fc5f05bea19fd963321728ebae Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 26 Mar 2020 16:38:09 +0000 Subject: [PATCH 06/70] Start migrating the repositories --- .../Admin/UserAdminController.swift | 17 +- .../Extensions/Models+Parameters.swift | 67 +++++++ Sources/SteamPress/Provider.swift | 2 +- .../Repositories/SteamPressRepository.swift | 168 ++++++++++++------ 4 files changed, 191 insertions(+), 63 deletions(-) create mode 100644 Sources/SteamPress/Extensions/Models+Parameters.swift diff --git a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift index 1aa9c24c..68c76946 100644 --- a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift @@ -50,8 +50,7 @@ struct UserAdminController: RouteCollection { if let resetPasswordRequired = data.resetPasswordOnLogin, resetPasswordRequired { newUser.resetPasswordRequired = true } - let userRepository = try req.make(BlogUserRepository.self) - return userRepository.save(newUser, on: req).map { _ in + return req.blogUserRepository.save(newUser, on: req).map { _ in return req.redirect(to: self.pathCreator.createPath(for: "admin")) } @@ -59,14 +58,14 @@ struct UserAdminController: RouteCollection { } func editUserHandler(_ req: Request) throws -> EventLoopFuture { - return try req.parameters.next(BlogUser.self).flatMap { user in + return req.parameters.find(BlogUser.self, on: req).flatMap { user in let presenter = try req.make(BlogAdminPresenter.self) return try presenter.createUserView(on: req, editing: true, errors: nil, name: user.name, nameError: false, username: user.username, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: user.resetPasswordRequired, userID: user.userID, profilePicture: user.profilePicture, twitterHandle: user.twitterHandle, biography: user.biography, tagline: user.tagline, pageInformation: req.adminPageInfomation()) } } func editUserPostHandler(_ req: Request) throws -> EventLoopFuture { - return try req.parameters.next(BlogUser.self).flatMap { user in + return req.parameters.find(BlogUser.self, on: req).flatMap { user in let data = try req.content.decode(CreateUserData.self) guard let name = data.name, let username = data.username else { @@ -110,8 +109,7 @@ struct UserAdminController: RouteCollection { } func deleteUserPostHandler(_ req: Request) throws -> EventLoopFuture { - let userRepository = try req.make(BlogUserRepository.self) - return try flatMap(req.parameters.next(BlogUser.self), userRepository.getUsersCount(on: req)) { user, userCount in + try req.parameters.find(BlogUser.self, on: req).and(req.blogUserRepository.getUsersCount(on: req)).flatMap { user, userCount in guard userCount > 1 else { let postRepository = try req.make(BlogPostRepository.self) return flatMap(postRepository.getAllPostsSortedByPublishDate(includeDrafts: true, on: req), userRepository.getAllUsers(on: req)) { posts, users in @@ -187,12 +185,11 @@ struct UserAdminController: RouteCollection { } var usernameUniqueError: EventLoopFuture - let usersRepository = try req.make(BlogUserRepository.self) if let username = data.username { if editing && data.username == existingUsername { - usernameUniqueError = req.future(nil) + usernameUniqueError = req.eventLoop.future(nil) } else { - usernameUniqueError = usersRepository.getUser(username: username.lowercased(), on: req).map { user in + usernameUniqueError = req.blogUserRepository.getUser(username: username.lowercased(), on: req).map { user in if user != nil { return "Sorry that username has already been taken" } else { @@ -201,7 +198,7 @@ struct UserAdminController: RouteCollection { } } } else { - usernameUniqueError = req.future(nil) + usernameUniqueError = req.eventLoop.future(nil) } return usernameUniqueError.map { usernameErrorOccurred in diff --git a/Sources/SteamPress/Extensions/Models+Parameters.swift b/Sources/SteamPress/Extensions/Models+Parameters.swift new file mode 100644 index 00000000..e3080bb5 --- /dev/null +++ b/Sources/SteamPress/Extensions/Models+Parameters.swift @@ -0,0 +1,67 @@ +import Vapor + +extension BlogUser: ParameterModel { +// typealias Repository = BlogUserRepository + public static let parameterKey = "blogUserID" + public static let parameter = PathComponent(stringLiteral: ":\(BlogPost.parameterKey)") + +// public typealias ResolvedParameter = EventLoopFuture +// public static func resolveParameter(_ parameter: String, on container: Container) throws -> BlogUser.ResolvedParameter { +// let userRepository = try container.make(BlogUserRepository.self) +// guard let userID = Int(parameter) else { +// throw SteamPressError(identifier: "Invalid-ID-Type", "Unable to convert \(parameter) to a User ID") +// } +// return userRepository.getUser(id: userID, on: container).unwrap(or: Abort(.notFound)) +// } +} + +extension BlogPost: ParameterModel { +// typealias Repository = BlogPostRepository + public static let parameterKey = "blogPostID" + public static let parameter = PathComponent(stringLiteral: ":\(BlogPost.parameterKey)") + +// public typealias ResolvedParameter = EventLoopFuture +// public static func resolveParameter(_ parameter: String, on container: Container) throws -> EventLoopFuture { +// let postRepository = try container.make(BlogPostRepository.self) +// guard let postID = Int(parameter) else { +// throw SteamPressError(identifier: "Invalid-ID-Type", "Unable to convert \(parameter) to a Post ID") +// } +// return postRepository.getPost(id: postID, on: container).unwrap(or: Abort(.notFound)) +// } +} + +extension BlogTag: ParameterModel { +// typealias Repository = BlogTagRepository + public static let parameterKey = "blogTagName" + public static let parameter = PathComponent(stringLiteral: ":\(BlogPost.parameterKey)") + +// public typealias ResolvedParameter = EventLoopFuture +// public static func resolveParameter(_ parameter: String, on container: Container) throws -> EventLoopFuture { +// let tagRepository = try container.make(BlogTagRepository.self) +// return tagRepository.getTag(parameter, on: container).unwrap(or: Abort(.notFound)) +// } +} + +protocol ParameterModel { + static var parameterKey: String { get } + static var parameter: PathComponent { get } +// associatedtype Repository: SteamPressRepository +} +// +//extension Parameters { +// func find(on req: Request, repository: SteamPressRepository) -> EventLoopFuture where T: ParameterModel { +// guard let idString = req.parameters.get(T.parameterKey), let id = Int(idString) else { +// return req.eventLoop.makeFailedFuture(Abort(.badRequest)) +// } +// return repository.get(id, on: req.eventLoop) +// } +//} + +extension Parameters { + func find(_ type: T.Type, on req: Request) -> EventLoopFuture where T: ParameterModel { + guard let idString = req.parameters.get(T.parameterKey), let id = Int(idString) else { + return req.eventLoop.makeFailedFuture(Abort(.badRequest)) + } + return req.blogUserRepository.getUser(id: id, on: req).unwrap(or: Abort(.notFound)) + } +} diff --git a/Sources/SteamPress/Provider.swift b/Sources/SteamPress/Provider.swift index 0fd9c9e8..3db47864 100644 --- a/Sources/SteamPress/Provider.swift +++ b/Sources/SteamPress/Provider.swift @@ -67,7 +67,7 @@ public struct Provider: Vapor.Provider { let blogController = BlogController(pathCreator: self.pathCreator, enableAuthorPages: self.enableAuthorPages, enableTagPages: self.enableTagPages, postsPerPage: self.postsPerPage) let blogAdminController = BlogAdminController(pathCreator: self.pathCreator) - let blogRoutes: Router + let blogRoutes: RoutesBuilder if let blogPath = blogPath { blogRoutes = router.grouped(blogPath) } else { diff --git a/Sources/SteamPress/Repositories/SteamPressRepository.swift b/Sources/SteamPress/Repositories/SteamPressRepository.swift index fd398a51..9adf76b6 100644 --- a/Sources/SteamPress/Repositories/SteamPressRepository.swift +++ b/Sources/SteamPress/Repositories/SteamPressRepository.swift @@ -1,71 +1,135 @@ import Vapor -public protocol BlogTagRepository { - func getAllTags(on container: Container) -> EventLoopFuture<[BlogTag]> - func getAllTagsWithPostCount(on container: Container) -> EventLoopFuture<[(BlogTag, Int)]> - func getTags(for post: BlogPost, on container: Container) -> EventLoopFuture<[BlogTag]> - func getTagsForAllPosts(on container: Container) -> EventLoopFuture<[Int: [BlogTag]]> - func getTag(_ name: String, on container: Container) -> EventLoopFuture - func save(_ tag: BlogTag, on container: Container) -> EventLoopFuture +public protocol SteamPressRepository { +// associatedtype ModelType +// func get(_ id: Int, on eventLoop: EventLoop) -> EventLoopFuture +} + +public protocol BlogTagRepository: SteamPressRepository { + func getAllTags() -> EventLoopFuture<[BlogTag]> + func getAllTagsWithPostCount() -> EventLoopFuture<[(BlogTag, Int)]> + func getTags(for post: BlogPost) -> EventLoopFuture<[BlogTag]> + func getTagsForAllPosts() -> EventLoopFuture<[Int: [BlogTag]]> + func getTag(_ name: String) -> EventLoopFuture + func save(_ tag: BlogTag) -> EventLoopFuture // Delete all the pivots between a post and collection of tags - you should probably delete the // tags that have no posts associated with a tag - func deleteTags(for post: BlogPost, on container: Container) -> EventLoopFuture - func remove(_ tag: BlogTag, from post: BlogPost, on container: Container) -> EventLoopFuture - func add(_ tag: BlogTag, to post: BlogPost, on container: Container) -> EventLoopFuture + func deleteTags(for post: BlogPost) -> EventLoopFuture + func remove(_ tag: BlogTag, from post: BlogPost) -> EventLoopFuture + func add(_ tag: BlogTag, to post: BlogPost) -> EventLoopFuture } -public protocol BlogPostRepository { - func getAllPostsSortedByPublishDate(includeDrafts: Bool, on container: Container) -> EventLoopFuture<[BlogPost]> - func getAllPostsCount(includeDrafts: Bool, on container: Container) -> EventLoopFuture - func getAllPostsSortedByPublishDate(includeDrafts: Bool, on container: Container, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> - func getAllPostsSortedByPublishDate(for user: BlogUser, includeDrafts: Bool, on container: Container, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> - func getPostCount(for user: BlogUser, on container: Container) -> EventLoopFuture - func getPost(slug: String, on container: Container) -> EventLoopFuture - func getPost(id: Int, on container: Container) -> EventLoopFuture - func getSortedPublishedPosts(for tag: BlogTag, on container: Container, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> - func getPublishedPostCount(for tag: BlogTag, on container: Container) -> EventLoopFuture - func findPublishedPostsOrdered(for searchTerm: String, on container: Container, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> - func getPublishedPostCount(for searchTerm: String, on container: Container) -> EventLoopFuture - func save(_ post: BlogPost, on container: Container) -> EventLoopFuture - func delete(_ post: BlogPost, on container: Container) -> EventLoopFuture +public protocol BlogPostRepository: SteamPressRepository { + func getAllPostsSortedByPublishDate(includeDrafts: Bool) -> EventLoopFuture<[BlogPost]> + func getAllPostsCount(includeDrafts: Bool) -> EventLoopFuture + func getAllPostsSortedByPublishDate(includeDrafts: Bool, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> + func getAllPostsSortedByPublishDate(for user: BlogUser, includeDrafts: Bool, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> + func getPostCount(for user: BlogUser) -> EventLoopFuture + func getPost(slug: String) -> EventLoopFuture + func getPost(id: Int) -> EventLoopFuture + func getSortedPublishedPosts(for tag: BlogTag, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> + func getPublishedPostCount(for tag: BlogTag) -> EventLoopFuture + func findPublishedPostsOrdered(for searchTerm: String, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> + func getPublishedPostCount(for searchTerm: String) -> EventLoopFuture + func save(_ post: BlogPost) -> EventLoopFuture + func delete(_ post: BlogPost) -> EventLoopFuture } -public protocol BlogUserRepository { - func getAllUsers(on container: Container) -> EventLoopFuture<[BlogUser]> - func getAllUsersWithPostCount(on container: Container) -> EventLoopFuture<[(BlogUser, Int)]> - func getUser(id: Int, on container: Container) -> EventLoopFuture - func getUser(username: String, on container: Container) -> EventLoopFuture - func save(_ user: BlogUser, on container: Container) -> EventLoopFuture - func delete(_ user: BlogUser, on container: Container) -> EventLoopFuture - func getUsersCount(on container: Container) -> EventLoopFuture +public protocol BlogUserRepository: SteamPressRepository { + init(application: Application) + func getAllUsers() -> EventLoopFuture<[BlogUser]> + func getAllUsersWithPostCount() -> EventLoopFuture<[(BlogUser, Int)]> + func getUser(id: Int) -> EventLoopFuture + func getUser(username: String) -> EventLoopFuture + func save(_ user: BlogUser) -> EventLoopFuture + func delete(_ user: BlogUser) -> EventLoopFuture + func getUsersCount() -> EventLoopFuture } -extension BlogUser: Parameter { - public typealias ResolvedParameter = EventLoopFuture - public static func resolveParameter(_ parameter: String, on container: Container) throws -> BlogUser.ResolvedParameter { - let userRepository = try container.make(BlogUserRepository.self) - guard let userID = Int(parameter) else { - throw SteamPressError(identifier: "Invalid-ID-Type", "Unable to convert \(parameter) to a User ID") - } - return userRepository.getUser(id: userID, on: container).unwrap(or: Abort(.notFound)) +//extension Request { +// var blogUserRepository: BlogUserRepository { +// +// } +//} + +public extension Request { + var blogUserRepository: BlogUserRepository { + self.application.blogUserRepositories.makeRepository!(self) + } + + var blogPostRepository: BlogPostRepository { + self.application.blogPostRepositories.makeRepository!(self) + } + + var blogTagRepository: BlogTagRepository { + self.application.blogTagRepositories.makeRepository!(self) } } -extension BlogPost: Parameter { - public typealias ResolvedParameter = EventLoopFuture - public static func resolveParameter(_ parameter: String, on container: Container) throws -> EventLoopFuture { - let postRepository = try container.make(BlogPostRepository.self) - guard let postID = Int(parameter) else { - throw SteamPressError(identifier: "Invalid-ID-Type", "Unable to convert \(parameter) to a Post ID") +private extension Application { + var blogUserRepositories: BlogUserRepositoryFactory { + get { + if let existing = self.userInfo["blogUserRepository"] as? BlogUserRepositoryFactory { + return existing + } else { + let new = BlogUserRepositoryFactory() + self.userInfo["blogUserRepository"] = new + return new + } + } + set { + self.userInfo["blogUserRepository"] = newValue + } + } + + var blogPostRepositories: BlogPostRepositoryFactory { + get { + if let existing = self.userInfo["blogPostRepository"] as? BlogPostRepositoryFactory { + return existing + } else { + let new = BlogPostRepositoryFactory() + self.userInfo["blogPostRepository"] = new + return new + } + } + set { + self.userInfo["blogPostRepository"] = newValue } - return postRepository.getPost(id: postID, on: container).unwrap(or: Abort(.notFound)) + } + + var blogTagRepositories: BlogTagRepositoryFactory { + get { + if let existing = self.userInfo["blogTagRepository"] as? BlogTagRepositoryFactory { + return existing + } else { + let new = BlogTagRepositoryFactory() + self.userInfo["blogTagRepository"] = new + return new + } + } + set { + self.userInfo["blogTagRepository"] = newValue + } + } +} + +private struct BlogUserRepositoryFactory { + var makeRepository: ((Request) -> BlogUserRepository)? + mutating func use(_ makeRepository: @escaping (Request) -> BlogUserRepository) { + self.makeRepository = makeRepository + } +} + +private struct BlogPostRepositoryFactory { + var makeRepository: ((Request) -> BlogPostRepository)? + mutating func use(_ makeRepository: @escaping (Request) -> BlogPostRepository) { + self.makeRepository = makeRepository } } -extension BlogTag: Parameter { - public typealias ResolvedParameter = EventLoopFuture - public static func resolveParameter(_ parameter: String, on container: Container) throws -> EventLoopFuture { - let tagRepository = try container.make(BlogTagRepository.self) - return tagRepository.getTag(parameter, on: container).unwrap(or: Abort(.notFound)) +private struct BlogTagRepositoryFactory { + var makeRepository: ((Request) -> BlogTagRepository)? + mutating func use(_ makeRepository: @escaping (Request) -> BlogTagRepository) { + self.makeRepository = makeRepository } } From 9ba81fb0bd1b2671fe1f12adeabd0bfa24e7bc74 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 26 Mar 2020 16:56:32 +0000 Subject: [PATCH 07/70] Migrate more repository stuff over --- .../Controllers/Admin/LoginController.swift | 6 +- .../Admin/PostAdminController.swift | 9 +- .../Admin/UserAdminController.swift | 8 +- .../Controllers/BlogAdminController.swift | 4 +- .../Controllers/BlogController.swift | 101 ++++++++---------- Sources/SteamPress/Models/BlogPost.swift | 3 +- 6 files changed, 57 insertions(+), 74 deletions(-) diff --git a/Sources/SteamPress/Controllers/Admin/LoginController.swift b/Sources/SteamPress/Controllers/Admin/LoginController.swift index f5e12ba1..c0e50249 100644 --- a/Sources/SteamPress/Controllers/Admin/LoginController.swift +++ b/Sources/SteamPress/Controllers/Admin/LoginController.swift @@ -61,8 +61,7 @@ struct LoginController: RouteCollection { try req.session()["SteamPressRememberMe"] = nil } - let userRepository = try req.make(BlogUserRepository.self) - return userRepository.getUser(username: username, on: req).flatMap { user in + return req.blogUserRepository.getUser(username: username, on: req).flatMap { user in let verifier = try req.make(PasswordVerifier.self) guard let user = user, try verifier.verify(password, created: user.password) else { let loginError = ["Your username or password is incorrect"] @@ -129,8 +128,7 @@ struct LoginController: RouteCollection { let hasher = try req.make(PasswordHasher.self) user.password = try hasher.hash(password) user.resetPasswordRequired = false - let userRespository = try req.make(BlogUserRepository.self) let redirect = req.redirect(to: pathCreator.createPath(for: "admin")) - return userRespository.save(user, on: req).transform(to: redirect) + return req.blogUserRepository.save(user, on: req).transform(to: redirect) } } diff --git a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift index 1478ef19..c91ad0cd 100644 --- a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift @@ -46,8 +46,7 @@ struct PostAdminController: RouteCollection { return try BlogPost.generateUniqueSlugURL(from: title, on: req).flatMap { uniqueSlug in let newPost = try BlogPost(title: title, contents: contents, author: author, creationDate: Date(), slugUrl: uniqueSlug, published: data.publish != nil) - let postRepository = try req.make(BlogPostRepository.self) - return postRepository.save(newPost, on: req).flatMap { post in + return req.blogPostRepository.save(newPost, on: req).flatMap { post in let tagsRepository = try req.make(BlogTagRepository.self) var existingTagsQuery = [EventLoopFuture]() @@ -86,8 +85,7 @@ struct PostAdminController: RouteCollection { let tagsRepository = try req.make(BlogTagRepository.self) return tagsRepository.deleteTags(for: post, on: req).flatMap { let redirect = req.redirect(to: self.pathCreator.createPath(for: "admin")) - let postRepository = try req.make(BlogPostRepository.self) - return postRepository.delete(post, on: req).transform(to: redirect) + return req.blogPostRepository.delete(post, on: req).transform(to: redirect) } } } @@ -174,8 +172,7 @@ struct PostAdminController: RouteCollection { } return postTagLinkResults.flatten(on: req).flatMap { let redirect = req.redirect(to: self.pathCreator.createPath(for: "posts/\(post.slugUrl)")) - let postRepository = try req.make(BlogPostRepository.self) - return postRepository.save(post, on: req).transform(to: redirect) + return req.blogPostRepository.save(post, on: req).transform(to: redirect) } } } diff --git a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift index 68c76946..68f00a79 100644 --- a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift @@ -109,10 +109,9 @@ struct UserAdminController: RouteCollection { } func deleteUserPostHandler(_ req: Request) throws -> EventLoopFuture { - try req.parameters.find(BlogUser.self, on: req).and(req.blogUserRepository.getUsersCount(on: req)).flatMap { user, userCount in + try req.parameters.find(BlogUser.self, on: req).and(req.blogUserRepository.getUsersCount()).flatMap { user, userCount in guard userCount > 1 else { - let postRepository = try req.make(BlogPostRepository.self) - return flatMap(postRepository.getAllPostsSortedByPublishDate(includeDrafts: true, on: req), userRepository.getAllUsers(on: req)) { posts, users in + return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: true).and(req.blogUserRepository.getAllUsers()).flatMap { posts, users in let presenter = try req.make(BlogAdminPresenter.self) let view = try presenter.createIndexView(on: req, posts: posts, users: users, errors: ["You cannot delete the last user"], pageInformation: req.adminPageInfomation()) return try view.encode(for: req) @@ -121,8 +120,7 @@ struct UserAdminController: RouteCollection { let loggedInUser = try req.requireAuthenticated(BlogUser.self) guard loggedInUser.userID != user.userID else { - let postRepository = try req.make(BlogPostRepository.self) - return flatMap(postRepository.getAllPostsSortedByPublishDate(includeDrafts: true, on: req), userRepository.getAllUsers(on: req)) { posts, users in + return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: true).and(req.blogUserRepository.getAllUsers()).flatMap { posts, users in let presenter = try req.make(BlogAdminPresenter.self) let view = try presenter.createIndexView(on: req, posts: posts, users: users, errors: ["You cannot delete yourself whilst logged in"], pageInformation: req.adminPageInfomation()) return try view.encode(for: req) diff --git a/Sources/SteamPress/Controllers/BlogAdminController.swift b/Sources/SteamPress/Controllers/BlogAdminController.swift index d678f175..7b4c09d3 100644 --- a/Sources/SteamPress/Controllers/BlogAdminController.swift +++ b/Sources/SteamPress/Controllers/BlogAdminController.swift @@ -29,9 +29,7 @@ struct BlogAdminController: RouteCollection { // MARK: Admin Handler func adminHandler(_ req: Request) throws -> EventLoopFuture { - let usersRepository = try req.make(BlogUserRepository.self) - let postsRepository = try req.make(BlogPostRepository.self) - return flatMap(postsRepository.getAllPostsSortedByPublishDate(includeDrafts: true, on: req), usersRepository.getAllUsers(on: req)) { posts, users in + return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: true).and(req.blogUserRepository.getAllUsers()).flatMap { posts, users in let presenter = try req.make(BlogAdminPresenter.self) return try presenter.createIndexView(on: req, posts: posts, users: users, errors: nil, pageInformation: req.adminPageInfomation()) } diff --git a/Sources/SteamPress/Controllers/BlogController.swift b/Sources/SteamPress/Controllers/BlogController.swift index 1c7be230..842c1669 100644 --- a/Sources/SteamPress/Controllers/BlogController.swift +++ b/Sources/SteamPress/Controllers/BlogController.swift @@ -3,11 +3,11 @@ import Vapor struct BlogController: RouteCollection { // MARK: - Properties - fileprivate let blogPostsPath = "posts" - fileprivate let tagsPath = "tags" - fileprivate let authorsPath = "authors" - fileprivate let apiPath = "api" - fileprivate let searchPath = "search" + fileprivate let blogPostsPath = PathComponent(stringLiteral: "posts") + fileprivate let tagsPath = PathComponent(stringLiteral: "tags") + fileprivate let authorsPath = PathComponent(stringLiteral: "authors") + fileprivate let apiPath = PathComponent(stringLiteral: "api") + fileprivate let searchPath = PathComponent(stringLiteral: "search") fileprivate let pathCreator: BlogPathCreator fileprivate let enableAuthorPages: Bool fileprivate let enableTagsPages: Bool @@ -24,12 +24,12 @@ struct BlogController: RouteCollection { // MARK: - Add routes func boot(routes: RoutesBuilder) throws { routes.get(use: indexHandler) - routes.get(blogPostsPath, String.parameter, use: blogPostHandler) + routes.get(blogPostsPath, ":blogSlug", use: blogPostHandler) routes.get(blogPostsPath, use: blogPostIndexRedirectHandler) routes.get(searchPath, use: searchHandler) if enableAuthorPages { routes.get(authorsPath, use: allAuthorsViewHandler) - routes.get(authorsPath, String.parameter, use: authorViewHandler) + routes.get(authorsPath, ":authorUsername", use: authorViewHandler) } if enableTagsPages { routes.get(tagsPath, BlogTag.parameter, use: tagViewHandler) @@ -40,17 +40,14 @@ struct BlogController: RouteCollection { // MARK: - Route Handlers func indexHandler(_ req: Request) throws -> EventLoopFuture { - let postRepository = try req.make(BlogPostRepository.self) - let tagRepository = try req.make(BlogTagRepository.self) - let userRepository = try req.make(BlogUserRepository.self) let paginationInformation = req.getPaginationInformation(postsPerPage: postsPerPage) - return flatMap(postRepository.getAllPostsSortedByPublishDate(includeDrafts: false, on: req, count: postsPerPage, offset: paginationInformation.offset), - tagRepository.getAllTags(on: req), - userRepository.getAllUsers(on: req), - postRepository.getAllPostsCount(includeDrafts: false, on: req), - tagRepository.getTagsForAllPosts(on: req)) { posts, tags, users, totalPostCount, tagsForPosts in - let presenter = try req.make(BlogPresenter.self) - return presenter.indexView(on: req, posts: posts, tags: tags, authors: users, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPostCount, currentQuery: req.http.url.query)) + return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: false, count: postsPerPage, offset: paginationInformation.offset).and(req.blogTagRepository.getAllTags()).flatMap { posts, tags in + req.blogUserRepository.getAllUsers().and(req.blogPostRepository.getAllPostsCount(includeDrafts: false)).flatMap { users, totalPostCount in + req.blogTagRepository.getTagsForAllPosts().flatMap { tagsForPosts in + let presenter = try req.make(BlogPresenter.self) + return presenter.indexView(on: req, posts: posts, tags: tags, authors: users, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPostCount, currentQuery: req.http.url.query)) + } + } } } @@ -59,14 +56,13 @@ struct BlogController: RouteCollection { } func blogPostHandler(_ req: Request) throws -> EventLoopFuture { - let blogSlug = try req.parameters.next(String.self) - let blogRepository = try req.make(BlogPostRepository.self) - return blogRepository.getPost(slug: blogSlug, on: req).unwrap(or: Abort(.notFound)).flatMap { post in - let userRepository = try req.make(BlogUserRepository.self) - let tagsRepository = try req.make(BlogTagRepository.self) - let tagsQuery = tagsRepository.getTags(for: post, on: req) - let userQuery = userRepository.getUser(id: post.author, on: req).unwrap(or: Abort(.internalServerError)) - return flatMap(userQuery, tagsQuery) { user, tags in + guard let blogSlug = req.parameters.get("blogSlug") else { + throw Abort(.badRequest) + } + return req.blogPostRepository.getPost(slug: blogSlug).unwrap(or: Abort(.notFound)).flatMap { post in + let tagsQuery = req.blogTagsRepository.getTags(for: post, on: req) + let userQuery = req.blogUserRepository.getUser(id: post.author, on: req).unwrap(or: Abort(.internalServerError)) + return userQuery.and(tagsQuery).flatMap { user, tags in let presenter = try req.make(BlogPresenter.self) return presenter.postView(on: req, post: post, author: user, tags: tags, pageInformation: try req.pageInformation()) } @@ -75,12 +71,10 @@ struct BlogController: RouteCollection { func tagViewHandler(_ req: Request) throws -> EventLoopFuture { return try req.parameters.next(BlogTag.self).flatMap { tag in - let postRepository = try req.make(BlogPostRepository.self) - let usersRepository = try req.make(BlogUserRepository.self) let paginationInformation = req.getPaginationInformation(postsPerPage: self.postsPerPage) - let postsQuery = postRepository.getSortedPublishedPosts(for: tag, on: req, count: self.postsPerPage, offset: paginationInformation.offset) - let postCountQuery = postRepository.getPublishedPostCount(for: tag, on: req) - let usersQuery = usersRepository.getAllUsers(on: req) + let postsQuery = req.blogPostRepository.getSortedPublishedPosts(for: tag, count: self.postsPerPage, offset: paginationInformation.offset) + let postCountQuery = req.blogPostRepository.getPublishedPostCount(for: tag) + let usersQuery = req.blogUsersRepository.getAllUsers() return flatMap(postsQuery, postCountQuery, usersQuery) { posts, totalPosts, authors in let presenter = try req.make(BlogPresenter.self) let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPosts, currentQuery: req.http.url.query) @@ -90,23 +84,23 @@ struct BlogController: RouteCollection { } func authorViewHandler(_ req: Request) throws -> EventLoopFuture { - let authorUsername = try req.parameters.next(String.self) - let userRepository = try req.make(BlogUserRepository.self) + guard let authorUsername = req.parameters.get("authorUsername") else { + throw Abort(.badRequest) + } let paginationInformation = req.getPaginationInformation(postsPerPage: postsPerPage) - return userRepository.getUser(username: authorUsername, on: req).flatMap { user in + return req.blogUserRepository.getUser(username: authorUsername).flatMap { user in guard let author = user else { throw Abort(.notFound) } - - let postRepository = try req.make(BlogPostRepository.self) - let tagsRepostiory = try req.make(BlogTagRepository.self) - let authorPostQuery = postRepository.getAllPostsSortedByPublishDate(for: author, includeDrafts: false, on: req, count: self.postsPerPage, offset: paginationInformation.offset) - let tagQuery = tagsRepostiory.getTagsForAllPosts(on: req) - let authorPostCountQuery = postRepository.getPostCount(for: author, on: req) - return flatMap(authorPostQuery, authorPostCountQuery, tagQuery) { posts, postCount, tagsForPosts in - let presenter = try req.make(BlogPresenter.self) - let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: postCount, currentQuery: req.http.url.query) - return presenter.authorView(on: req, author: author, posts: posts, postCount: postCount, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + let authorPostQuery = req.blogPostRepository.getAllPostsSortedByPublishDate(for: author, includeDrafts: false, count: self.postsPerPage, offset: paginationInformation.offset) + let tagQuery = req.blogTagRepository.getTagsForAllPosts() + let authorPostCountQuery = req.blogPostRepository.getPostCount(for: author) + return authorPostQuery.and(authorPostCountQuery).flatMap { posts, postCount in + tagQuery.flatMap { tagsForPosts in + let presenter = try req.make(BlogPresenter.self) + let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: postCount, currentQuery: req.http.url.query) + return presenter.authorView(on: req, author: author, posts: posts, postCount: postCount, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + } } } } @@ -145,20 +139,19 @@ struct BlogController: RouteCollection { let preseneter = try req.make(BlogPresenter.self) let paginationInformation = req.getPaginationInformation(postsPerPage: postsPerPage) guard let searchTerm = req.query[String.self, at: "term"], !searchTerm.isEmpty else { - let paginationTagInfo = getPaginationInformation(currentPage: paginationInformation.page, totalPosts: 0, currentQuery: req.http.url.query) + let paginationTagInfo = getPaginationInformation(currentPage: paginationInformation.page, totalPosts: 0, currentQuery: req.url.query) return preseneter.searchView(on: req, totalResults: 0, posts: [], authors: [], searchTerm: nil, tagsForPosts: [:], pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) } - let postRepository = try req.make(BlogPostRepository.self) - let authorRepository = try req.make(BlogUserRepository.self) - let tagRepository = try req.make(BlogTagRepository.self) - let postsCountQuery = postRepository.getPublishedPostCount(for: searchTerm, on: req) - let postsQuery = postRepository.findPublishedPostsOrdered(for: searchTerm, on: req, count: self.postsPerPage, offset: paginationInformation.offset) - let tagsQuery = tagRepository.getTagsForAllPosts(on: req) - let userQuery = authorRepository.getAllUsers(on: req) - return flatMap(postsQuery, postsCountQuery, userQuery, tagsQuery) { posts, totalPosts, users, tagsForPosts in - let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPosts, currentQuery: req.http.url.query) - return preseneter.searchView(on: req, totalResults: totalPosts, posts: posts, authors: users, searchTerm: searchTerm, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + let postsCountQuery = req.blogPostRepository.getPublishedPostCount(for: searchTerm) + let postsQuery = req.blogPostRepository.findPublishedPostsOrdered(for: searchTerm, count: self.postsPerPage, offset: paginationInformation.offset) + let tagsQuery = req.blogTagRepository.getTagsForAllPosts() + let userQuery = req.blogUserRepository.getAllUsers() + return postsQuery.and(postsCountQuery).flatMap { posts, totalPosts in + userQuery.and(tagsQuery).flatMap { users, tagsForPosts in + let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPosts, currentQuery: req.http.url.query) + return preseneter.searchView(on: req, totalResults: totalPosts, posts: posts, authors: users, searchTerm: searchTerm, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + } } } diff --git a/Sources/SteamPress/Models/BlogPost.swift b/Sources/SteamPress/Models/BlogPost.swift index f6a9e39b..303ad713 100644 --- a/Sources/SteamPress/Models/BlogPost.swift +++ b/Sources/SteamPress/Models/BlogPost.swift @@ -61,14 +61,13 @@ extension BlogPost { } static func generateUniqueSlugURL(from title: String, on req: Request) throws -> EventLoopFuture { - let postRepository = try req.make(BlogPostRepository.self) let alphanumericsWithHyphenAndSpace = CharacterSet(charactersIn: " -0123456789abcdefghijklmnopqrstuvwxyz") let initialSlug = title.lowercased() .trimmingCharacters(in: .whitespacesAndNewlines) .components(separatedBy: alphanumericsWithHyphenAndSpace.inverted).joined() .components(separatedBy: .whitespacesAndNewlines).filter { !$0.isEmpty }.joined(separator: " ") .replacingOccurrences(of: " ", with: "-", options: .regularExpression) - return postRepository.getPost(slug: initialSlug, on: req).map { postWithSameSlug in + return req.blogPostRepository.getPost(slug: initialSlug).map { postWithSameSlug in if postWithSameSlug != nil { let randomNumberGenerator = try req.make(SteamPressRandomNumberGenerator.self) let randomNumber = randomNumberGenerator.getNumber() From 2ce9659c4b45bcc4a202140eaf30b51dc9c61be8 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 26 Mar 2020 17:04:12 +0000 Subject: [PATCH 08/70] Migrate last make repository calls --- .../Controllers/API/APITagController.swift | 3 +-- .../Admin/PostAdminController.swift | 23 ++++++++----------- .../Admin/UserAdminController.swift | 5 ++-- .../Controllers/BlogController.swift | 8 +++---- 4 files changed, 15 insertions(+), 24 deletions(-) diff --git a/Sources/SteamPress/Controllers/API/APITagController.swift b/Sources/SteamPress/Controllers/API/APITagController.swift index c1155aec..7fa74e6e 100644 --- a/Sources/SteamPress/Controllers/API/APITagController.swift +++ b/Sources/SteamPress/Controllers/API/APITagController.swift @@ -7,7 +7,6 @@ struct APITagController: RouteCollection { } func allTagsHandler(_ req: Request) throws -> EventLoopFuture<[BlogTag]> { - let repository = try req.make(BlogTagRepository.self) - return repository.getAllTags(on: req) + req.blogTagRepository.getAllTags() } } diff --git a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift index c91ad0cd..a8304f25 100644 --- a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift @@ -46,12 +46,10 @@ struct PostAdminController: RouteCollection { return try BlogPost.generateUniqueSlugURL(from: title, on: req).flatMap { uniqueSlug in let newPost = try BlogPost(title: title, contents: contents, author: author, creationDate: Date(), slugUrl: uniqueSlug, published: data.publish != nil) - return req.blogPostRepository.save(newPost, on: req).flatMap { post in - let tagsRepository = try req.make(BlogTagRepository.self) - + return req.blogPostRepository.save(newPost).flatMap { post in var existingTagsQuery = [EventLoopFuture]() for tagName in data.tags { - existingTagsQuery.append(tagsRepository.getTag(tagName, on: req)) + existingTagsQuery.append(req.blogTagRepository.getTag(tagName)) } return existingTagsQuery.flatten(on: req).flatMap { existingTagsWithOptionals in @@ -60,17 +58,17 @@ struct PostAdminController: RouteCollection { for tagName in data.tags { if !existingTags.contains(where: { $0.name == tagName }) { let tag = BlogTag(name: tagName) - tagsSaves.append(tagsRepository.save(tag, on: req)) + tagsSaves.append(req.blogTagRepository.save(tag)) } } return tagsSaves.flatten(on: req).flatMap { tags in var tagLinks = [EventLoopFuture]() for tag in tags { - tagLinks.append(tagsRepository.add(tag, to: post, on: req)) + tagLinks.append(req.blogTagRepository.add(tag, to: post)) } for tag in existingTags { - tagLinks.append(tagsRepository.add(tag, to: post, on: req)) + tagLinks.append(req.blogTagRepository.add(tag, to: post)) } let redirect = req.redirect(to: self.pathCreator.createPath(for: "posts/\(post.slugUrl)")) return tagLinks.flatten(on: req).transform(to: redirect) @@ -82,18 +80,16 @@ struct PostAdminController: RouteCollection { func deletePostHandler(_ req: Request) throws -> EventLoopFuture { return try req.parameters.next(BlogPost.self).flatMap { post in - let tagsRepository = try req.make(BlogTagRepository.self) - return tagsRepository.deleteTags(for: post, on: req).flatMap { + return req.blogTagRepository.deleteTags(for: post).flatMap { let redirect = req.redirect(to: self.pathCreator.createPath(for: "admin")) - return req.blogPostRepository.delete(post, on: req).transform(to: redirect) + return req.blogPostRepository.delete(post).transform(to: redirect) } } } func editPostHandler(_ req: Request) throws -> EventLoopFuture { return try req.parameters.next(BlogPost.self).flatMap { post in - let tagsRepository = try req.make(BlogTagRepository.self) - return tagsRepository.getTags(for: post, on: req).flatMap { tags in + return req.blogTagRepository.getTags(for: post).flatMap { tags in let presenter = try req.make(BlogAdminPresenter.self) return try presenter.createPostView(on: req, errors: nil, title: post.title, contents: post.contents, slugURL: post.slugUrl, tags: tags.map { $0.name }, isEditing: true, post: post, isDraft: !post.published, titleError: false, contentsError: false, pageInformation: req.adminPageInfomation()) } @@ -133,8 +129,7 @@ struct PostAdminController: RouteCollection { } } - let tagsRepository = try req.make(BlogTagRepository.self) - return flatMap(tagsRepository.getTags(for: post, on: req), tagsRepository.getAllTags(on: req)) { existingTags, allTags in + return req.blogTagRepository.getTags(for: post).and(req.blogTagRepository.getAllTags()) { existingTags, allTags in let tagsToUnlink = existingTags.filter { (anExistingTag) -> Bool in for tagName in data.tags { if anExistingTag.name == tagName { diff --git a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift index 68f00a79..0d0b3777 100644 --- a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift @@ -102,14 +102,13 @@ struct UserAdminController: RouteCollection { } let redirect = req.redirect(to: self.pathCreator.createPath(for: "admin")) - let userRepository = try req.make(BlogUserRepository.self) - return userRepository.save(user, on: req).transform(to: redirect) + return req.blogUserRepository.save(user).transform(to: redirect) } } } func deleteUserPostHandler(_ req: Request) throws -> EventLoopFuture { - try req.parameters.find(BlogUser.self, on: req).and(req.blogUserRepository.getUsersCount()).flatMap { user, userCount in + req.parameters.find(BlogUser.self, on: req).and(req.blogUserRepository.getUsersCount()).flatMap { user, userCount in guard userCount > 1 else { return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: true).and(req.blogUserRepository.getAllUsers()).flatMap { posts, users in let presenter = try req.make(BlogAdminPresenter.self) diff --git a/Sources/SteamPress/Controllers/BlogController.swift b/Sources/SteamPress/Controllers/BlogController.swift index 842c1669..8a6bce24 100644 --- a/Sources/SteamPress/Controllers/BlogController.swift +++ b/Sources/SteamPress/Controllers/BlogController.swift @@ -106,8 +106,7 @@ struct BlogController: RouteCollection { } func allTagsViewHandler(_ req: Request) throws -> EventLoopFuture { - let tagRepository = try req.make(BlogTagRepository.self) - return tagRepository.getAllTagsWithPostCount(on: req).flatMap { tagswithCount in + return req.blogTagRepository.getAllTagsWithPostCount().flatMap { tagswithCount in let presenter = try req.make(BlogPresenter.self) let allTags = tagswithCount.map { $0.0 } let tagCounts = try tagswithCount.reduce(into: [Int: Int]()) { @@ -122,8 +121,7 @@ struct BlogController: RouteCollection { func allAuthorsViewHandler(_ req: Request) throws -> EventLoopFuture { let presenter = try req.make(BlogPresenter.self) - let authorRepository = try req.make(BlogUserRepository.self) - return authorRepository.getAllUsersWithPostCount(on: req).flatMap { allUsersWithCount in + return req.blogUserRepository.getAllUsersWithPostCount().flatMap { allUsersWithCount in let allUsers = allUsersWithCount.map { $0.0 } let authorCounts = try allUsersWithCount.reduce(into: [Int: Int]()) { guard let userID = $1.0.userID else { @@ -149,7 +147,7 @@ struct BlogController: RouteCollection { let userQuery = req.blogUserRepository.getAllUsers() return postsQuery.and(postsCountQuery).flatMap { posts, totalPosts in userQuery.and(tagsQuery).flatMap { users, tagsForPosts in - let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPosts, currentQuery: req.http.url.query) + let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPosts, currentQuery: req.url.query) return preseneter.searchView(on: req, totalResults: totalPosts, posts: posts, authors: users, searchTerm: searchTerm, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) } } From 99a3825a6837be24b05fc74c5954189b6f98e1ba Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 26 Mar 2020 17:11:16 +0000 Subject: [PATCH 09/70] Get the feed generators converted --- .../Feed Generators/AtomFeedGenerator.swift | 29 +++++++++---------- .../Feed Generators/RSSFeedGenerator.swift | 15 +++++----- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Sources/SteamPress/Feed Generators/AtomFeedGenerator.swift b/Sources/SteamPress/Feed Generators/AtomFeedGenerator.swift index 4d9e369c..581f4527 100644 --- a/Sources/SteamPress/Feed Generators/AtomFeedGenerator.swift +++ b/Sources/SteamPress/Feed Generators/AtomFeedGenerator.swift @@ -28,11 +28,10 @@ struct AtomFeedGenerator { // MARK: - Route Handler - func feedHandler(_ request: Request) throws -> EventLoopFuture { + func feedHandler(_ request: Request) throws -> EventLoopFuture { - let blogRepository = try request.make(BlogPostRepository.self) - return blogRepository.getAllPostsSortedByPublishDate(includeDrafts: false, on: request).flatMap { posts in - var feed = self.getFeedStart(for: request) + return request.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: false).flatMap { posts in + var feed = try self.getFeedStart(for: request) if !posts.isEmpty { let postDate = posts[0].lastEdited ?? posts[0].created @@ -54,13 +53,13 @@ struct AtomFeedGenerator { try postData.append(post.getPostAtomFeed(blogPath: self.getRootPath(for: request), dateFormatter: self.iso8601Formatter, for: request)) } - return postData.flatten(on: request).map { postsInformation in + return postData.flatten(on: request.eventLoop).map { postsInformation in for postInformation in postsInformation { feed += postInformation } feed += self.feedEnd - var httpResponse = HTTPResponse(body: feed) + var httpResponse = Response(body: .init(stringLiteral: feed)) httpResponse.headers.add(name: .contentType, value: "application/atom+xml") return httpResponse } @@ -69,15 +68,17 @@ struct AtomFeedGenerator { // MARK: - Private functions - private func getFeedStart(for request: Request) -> String { - let blogLink = getRootPath(for: request) + "/" + private func getFeedStart(for request: Request) throws -> String { + let blogLink = try getRootPath(for: request) + "/" let feedLink = blogLink + "atom.xml" return "\(xmlDeclaration)\n\(feedStart)\n\n\(title)\n\(description)\n\(blogLink)\n\n\nSteamPress\n" } - private func getRootPath(for request: Request) -> String { - let hostname = request.http.remotePeer.description - let path = request.http.url.path + private func getRootPath(for request: Request) throws -> String { + guard let hostname = Environment.get("WEBSITE_URL") else { + throw SteamPressError(identifier: "SteamPressError", "WEBSITE_URL not set") + } + let path = request.url.path return "\(hostname)\(path.replacingOccurrences(of: "/atom.xml", with: ""))" } } @@ -85,8 +86,7 @@ struct AtomFeedGenerator { fileprivate extension BlogPost { func getPostAtomFeed(blogPath: String, dateFormatter: DateFormatter, for request: Request) throws -> EventLoopFuture { let updatedTime = lastEdited ?? created - let authorRepository = try request.make(BlogUserRepository.self) - return authorRepository.getUser(id: author, on: request).flatMap { user in + return request.blogUserRepository.getUser(id: author).flatMap { user in guard let user = user else { throw SteamPressError(identifier: "Invalid-relationship", "Blog user with ID \(self.author) not found") } @@ -95,8 +95,7 @@ fileprivate extension BlogPost { } var postEntry = "\n\(blogPath)/posts-id/\(postID)/\n\(self.title)\n\(dateFormatter.string(from: updatedTime))\n\(dateFormatter.string(from: self.created))\n\n\(user.name)\n\(blogPath)/authors/\(user.username)/\n\n\(try self.description())\n\n" - let tagRepository = try request.make(BlogTagRepository.self) - return tagRepository.getTags(for: self, on: request).map { tags in + return request.blogTagRepository.getTags(for: self).map { tags in for tag in tags { if let percentDecodedTag = tag.name.removingPercentEncoding { postEntry += "\n" diff --git a/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift b/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift index 8919ecc6..93220770 100644 --- a/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift +++ b/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift @@ -61,9 +61,9 @@ struct RSSFeedGenerator { // MARK: - Private functions - private func getXMLStart(for request: Request) -> String { + private func getXMLStart(for request: Request) throws -> String { - let link = getRootPath(for: request) + "/" + let link = try getRootPath(for: request) + "/" var start = "\n\n\n\n\(title)\n\(link)\n\(description)\nSteamPress\n60\n" @@ -78,9 +78,11 @@ struct RSSFeedGenerator { return start } - private func getRootPath(for request: Request) -> String { - let hostname = request.http.remotePeer.description - let path = request.http.url.path + private func getRootPath(for request: Request) throws -> String { + guard let hostname = Environment.get("WEBSITE_URL") else { + throw SteamPressError(identifier: "SteamPressError", "WEBSITE_URL not set") + } + let path = request.url.path return "\(hostname)\(path.replacingOccurrences(of: "/rss.xml", with: ""))" } } @@ -90,8 +92,7 @@ fileprivate extension BlogPost { let link = rootPath + "/posts/\(slugUrl)/" var postEntry = "\n\n\(title)\n\n\n\(try description())\n\n\n\(link)\n\n" - let tagRepository = try request.make(BlogTagRepository.self) - return tagRepository.getTags(for: self, on: request).map { tags in + return request.blogTagRepository.getTags(for: self).map { tags in for tag in tags { if let percentDecodedTag = tag.name.removingPercentEncoding { postEntry += "\(percentDecodedTag)\n" From 5230462d2e7e7271ccaf3bd1aec120b99b97fd70 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 26 Mar 2020 17:22:43 +0000 Subject: [PATCH 10/70] Start migrating the presenter services over --- .../Controllers/Admin/LoginController.swift | 19 +++----- .../Presenters/BlogAdminPresenter.swift | 40 ++++++++++++++-- .../SteamPress/Presenters/BlogPresenter.swift | 48 +++++++++++++++---- .../Presenters/ViewBlogAdminPresenter.swift | 13 ++--- .../Presenters/ViewBlogPresenter.swift | 27 ++++------- 5 files changed, 96 insertions(+), 51 deletions(-) diff --git a/Sources/SteamPress/Controllers/Admin/LoginController.swift b/Sources/SteamPress/Controllers/Admin/LoginController.swift index c0e50249..90acb2ee 100644 --- a/Sources/SteamPress/Controllers/Admin/LoginController.swift +++ b/Sources/SteamPress/Controllers/Admin/LoginController.swift @@ -26,8 +26,7 @@ struct LoginController: RouteCollection { // MARK: - Route handlers func loginHandler(_ req: Request) throws -> EventLoopFuture { let loginRequied = (try? req.query.get(Bool.self, at: "loginRequired")) != nil - let presenter = try req.make(BlogPresenter.self) - return try presenter.loginView(on: req, loginWarning: loginRequied, errors: nil, username: nil, usernameError: false, passwordError: false, rememberMe: false, pageInformation: req.pageInformation()) + return try req.blogPresenter.loginView(loginWarning: loginRequied, errors: nil, username: nil, usernameError: false, passwordError: false, rememberMe: false, pageInformation: req.pageInformation()) } func loginPostHandler(_ req: Request) throws -> EventLoopFuture { @@ -47,8 +46,7 @@ struct LoginController: RouteCollection { } if !loginErrors.isEmpty { - let presenter = try req.make(BlogPresenter.self) - return try presenter.loginView(on: req, loginWarning: false, errors: loginErrors, username: loginData.username, usernameError: usernameError, passwordError: passwordError, rememberMe: loginData.rememberMe ?? false, pageInformation: req.pageInformation()).encode(for: req) + return try req.blogPresenter.loginView(loginWarning: false, errors: loginErrors, username: loginData.username, usernameError: usernameError, passwordError: passwordError, rememberMe: loginData.rememberMe ?? false, pageInformation: req.pageInformation()).encodeResponse(for: req) } guard let username = loginData.username, let password = loginData.password else { @@ -61,7 +59,7 @@ struct LoginController: RouteCollection { try req.session()["SteamPressRememberMe"] = nil } - return req.blogUserRepository.getUser(username: username, on: req).flatMap { user in + return req.blogUserRepository.getUser(username: username).flatMap { user in let verifier = try req.make(PasswordVerifier.self) guard let user = user, try verifier.verify(password, created: user.password) else { let loginError = ["Your username or password is incorrect"] @@ -79,8 +77,7 @@ struct LoginController: RouteCollection { } func resetPasswordHandler(_ req: Request) throws -> EventLoopFuture { - let presenter = try req.make(BlogAdminPresenter.self) - return try presenter.createResetPasswordView(on: req, errors: nil, passwordError: nil, confirmPasswordError: nil, pageInformation: req.adminPageInfomation()) + try req.adminPresenter.createResetPasswordView(errors: nil, passwordError: nil, confirmPasswordError: nil, pageInformation: req.adminPageInfomation()) } func resetPasswordPostHandler(_ req: Request) throws -> EventLoopFuture { @@ -102,9 +99,8 @@ struct LoginController: RouteCollection { confirmPasswordError = true } - let presenter = try req.make(BlogAdminPresenter.self) - let view = try presenter.createResetPasswordView(on: req, errors: resetPasswordErrors, passwordError: passwordError, confirmPasswordError: confirmPasswordError, pageInformation: req.adminPageInfomation()) - return try view.encode(for: req) + let view = try req.adminPresenter.createResetPasswordView(errors: resetPasswordErrors, passwordError: passwordError, confirmPasswordError: confirmPasswordError, pageInformation: req.adminPageInfomation()) + return view.encodeResponse(for: req) } if password != confirmPassword { @@ -119,8 +115,7 @@ struct LoginController: RouteCollection { } guard resetPasswordErrors.isEmpty else { - let presenter = try req.make(BlogAdminPresenter.self) - let view = try presenter.createResetPasswordView(on: req, errors: resetPasswordErrors, passwordError: passwordError, confirmPasswordError: confirmPasswordError, pageInformation: req.adminPageInfomation()) + let view = try req.adminPresenter.createResetPasswordView(errors: resetPasswordErrors, passwordError: passwordError, confirmPasswordError: confirmPasswordError, pageInformation: req.adminPageInfomation()) return try view.encode(for: req) } diff --git a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift index 47a1792d..00bac385 100644 --- a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift @@ -1,8 +1,38 @@ import Vapor -public protocol BlogAdminPresenter: Service { - func createIndexView(on container: Container, posts: [BlogPost], users: [BlogUser], errors: [String]?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture - func createPostView(on container: Container, errors: [String]?, title: String?, contents: String?, slugURL: String?, tags: [String]?, isEditing: Bool, post: BlogPost?, isDraft: Bool?, titleError: Bool, contentsError: Bool, pageInformation: BlogAdminPageInformation) -> EventLoopFuture - func createUserView(on container: Container, editing: Bool, errors: [String]?, name: String?, nameError: Bool, username: String?, usernameErorr: Bool, passwordError: Bool, confirmPasswordError: Bool, resetPasswordOnLogin: Bool, userID: Int?, profilePicture: String?, twitterHandle: String?, biography: String?, tagline: String?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture - func createResetPasswordView(on container: Container, errors: [String]?, passwordError: Bool?, confirmPasswordError: Bool?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture +public protocol BlogAdminPresenter { + func createIndexView(posts: [BlogPost], users: [BlogUser], errors: [String]?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture + func createPostView(errors: [String]?, title: String?, contents: String?, slugURL: String?, tags: [String]?, isEditing: Bool, post: BlogPost?, isDraft: Bool?, titleError: Bool, contentsError: Bool, pageInformation: BlogAdminPageInformation) -> EventLoopFuture + func createUserView(editing: Bool, errors: [String]?, name: String?, nameError: Bool, username: String?, usernameErorr: Bool, passwordError: Bool, confirmPasswordError: Bool, resetPasswordOnLogin: Bool, userID: Int?, profilePicture: String?, twitterHandle: String?, biography: String?, tagline: String?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture + func createResetPasswordView(errors: [String]?, passwordError: Bool?, confirmPasswordError: Bool?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture +} + +extension Request { + var adminPresenter: BlogAdminPresenter { + self.application.adminPresenter.makePresenter!(self) + } +} + +extension Application { + var adminPresenter: AdminPresenterFactory { + get { + if let existing = self.userInfo["blogAdminPresenter"] as? AdminPresenterFactory { + return existing + } else { + let new = AdminPresenterFactory() + self.userInfo["blogAdminPresenter"] = new + return new + } + } + set { + self.userInfo["blogAdminPresenter"] = newValue + } + } +} + +struct AdminPresenterFactory { + var makePresenter: ((Request) -> BlogAdminPresenter)? + mutating func use(_ makePresenter: @escaping (Request) -> BlogAdminPresenter) { + self.makePresenter = makePresenter + } } diff --git a/Sources/SteamPress/Presenters/BlogPresenter.swift b/Sources/SteamPress/Presenters/BlogPresenter.swift index b9d48058..68cf6122 100644 --- a/Sources/SteamPress/Presenters/BlogPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogPresenter.swift @@ -1,12 +1,42 @@ import Vapor -public protocol BlogPresenter: Service { - func indexView(on container: Container, posts: [BlogPost], tags: [BlogTag], authors: [BlogUser], tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture - func postView(on container: Container, post: BlogPost, author: BlogUser, tags: [BlogTag], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture - func allAuthorsView(on container: Container, authors: [BlogUser], authorPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture - func authorView(on container: Container, author: BlogUser, posts: [BlogPost], postCount: Int, tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture - func allTagsView(on container: Container, tags: [BlogTag], tagPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture - func tagView(on container: Container, tag: BlogTag, posts: [BlogPost], authors: [BlogUser], totalPosts: Int, pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture - func searchView(on container: Container, totalResults: Int, posts: [BlogPost], authors: [BlogUser], searchTerm: String?, tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture - func loginView(on container: Container, loginWarning: Bool, errors: [String]?, username: String?, usernameError: Bool, passwordError: Bool, rememberMe: Bool, pageInformation: BlogGlobalPageInformation) -> EventLoopFuture +public protocol BlogPresenter { + func indexView(posts: [BlogPost], tags: [BlogTag], authors: [BlogUser], tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture + func postView(post: BlogPost, author: BlogUser, tags: [BlogTag], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture + func allAuthorsView(authors: [BlogUser], authorPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture + func authorView(author: BlogUser, posts: [BlogPost], postCount: Int, tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture + func allTagsView(tags: [BlogTag], tagPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture + func tagView(tag: BlogTag, posts: [BlogPost], authors: [BlogUser], totalPosts: Int, pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture + func searchView(totalResults: Int, posts: [BlogPost], authors: [BlogUser], searchTerm: String?, tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture + func loginView(loginWarning: Bool, errors: [String]?, username: String?, usernameError: Bool, passwordError: Bool, rememberMe: Bool, pageInformation: BlogGlobalPageInformation) -> EventLoopFuture +} + +extension Request { + var blogPresenter: BlogPresenter { + self.application.presenter.makePresenter!(self) + } +} + +extension Application { + var presenter: PresenterFactory { + get { + if let existing = self.userInfo["blogPresenter"] as? PresenterFactory { + return existing + } else { + let new = AdminPresenterFactory() + self.userInfo["blogPresenter"] = new + return new + } + } + set { + self.userInfo["blogPresenter"] = newValue + } + } +} + +struct PresenterFactory { + var makePresenter: ((Request) -> BlogPresenter)? + mutating func use(_ makePresenter: @escaping (Request) -> BlogPresenter) { + self.makePresenter = makePresenter + } } diff --git a/Sources/SteamPress/Presenters/ViewBlogAdminPresenter.swift b/Sources/SteamPress/Presenters/ViewBlogAdminPresenter.swift index 1853c899..bf33d92a 100644 --- a/Sources/SteamPress/Presenters/ViewBlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/ViewBlogAdminPresenter.swift @@ -3,10 +3,10 @@ import Vapor public struct ViewBlogAdminPresenter: BlogAdminPresenter { let pathCreator: BlogPathCreator + let viewRenderer: ViewRenderer - public func createIndexView(on container: Container, posts: [BlogPost], users: [BlogUser], errors: [String]?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { + public func createIndexView(posts: [BlogPost], users: [BlogUser], errors: [String]?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { do { - let viewRenderer = try container.make(ViewRenderer.self) let publishedPosts = try posts.filter { $0.published }.convertToViewBlogPostsWithoutTags(authors: users, on: container) let draftPosts = try posts.filter { !$0.published }.convertToViewBlogPostsWithoutTags(authors: users, on: container) let context = AdminPageContext(errors: errors, publishedPosts: publishedPosts, draftPosts: draftPosts, users: users, pageInformation: pageInformation) @@ -16,14 +16,13 @@ public struct ViewBlogAdminPresenter: BlogAdminPresenter { } } - public func createPostView(on container: Container, errors: [String]?, title: String?, contents: String?, slugURL: String?, tags: [String]?, isEditing: Bool, post: BlogPost?, isDraft: Bool?, titleError: Bool, contentsError: Bool, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { + public func createPostView(errors: [String]?, title: String?, contents: String?, slugURL: String?, tags: [String]?, isEditing: Bool, post: BlogPost?, isDraft: Bool?, titleError: Bool, contentsError: Bool, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { do { if isEditing { guard post != nil else { return container.future(error: SteamPressError(identifier: "ViewBlogAdminPresenter", "Blog Post is empty whilst editing")) } } - let viewRenderer = try container.make(ViewRenderer.self) let postPathSuffix = pathCreator.createPath(for: "posts") let postPathPrefix = pageInformation.websiteURL.appendingPathComponent(postPathSuffix) let pageTitle = isEditing ? "Edit Blog Post" : "Create Blog Post" @@ -34,7 +33,7 @@ public struct ViewBlogAdminPresenter: BlogAdminPresenter { } } - public func createUserView(on container: Container, editing: Bool, errors: [String]?, name: String?, nameError: Bool, username: String?, usernameErorr: Bool, passwordError: Bool, confirmPasswordError: Bool, resetPasswordOnLogin: Bool, userID: Int?, profilePicture: String?, twitterHandle: String?, biography: String?, tagline: String?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { + public func createUserView(editing: Bool, errors: [String]?, name: String?, nameError: Bool, username: String?, usernameErorr: Bool, passwordError: Bool, confirmPasswordError: Bool, resetPasswordOnLogin: Bool, userID: Int?, profilePicture: String?, twitterHandle: String?, biography: String?, tagline: String?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { do { if editing { guard userID != nil else { @@ -42,7 +41,6 @@ public struct ViewBlogAdminPresenter: BlogAdminPresenter { } } - let viewRenderer = try container.make(ViewRenderer.self) let context = CreateUserPageContext(editing: editing, errors: errors, nameSupplied: name, nameError: nameError, usernameSupplied: username, usernameError: usernameErorr, passwordError: passwordError, confirmPasswordError: confirmPasswordError, resetPasswordOnLoginSupplied: resetPasswordOnLogin, userID: userID, twitterHandleSupplied: twitterHandle, profilePictureSupplied: profilePicture, biographySupplied: biography, taglineSupplied: tagline, pageInformation: pageInformation) return viewRenderer.render("blog/admin/createUser", context) } catch { @@ -50,9 +48,8 @@ public struct ViewBlogAdminPresenter: BlogAdminPresenter { } } - public func createResetPasswordView(on container: Container, errors: [String]?, passwordError: Bool?, confirmPasswordError: Bool?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { + public func createResetPasswordView(errors: [String]?, passwordError: Bool?, confirmPasswordError: Bool?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { do { - let viewRenderer = try container.make(ViewRenderer.self) let context = ResetPasswordPageContext(errors: errors, passwordError: passwordError, confirmPasswordError: confirmPasswordError, pageInformation: pageInformation) return viewRenderer.render("blog/admin/resetPassword", context) } catch { diff --git a/Sources/SteamPress/Presenters/ViewBlogPresenter.swift b/Sources/SteamPress/Presenters/ViewBlogPresenter.swift index 033b757a..11ca7d12 100644 --- a/Sources/SteamPress/Presenters/ViewBlogPresenter.swift +++ b/Sources/SteamPress/Presenters/ViewBlogPresenter.swift @@ -3,10 +3,11 @@ import SwiftSoup import SwiftMarkdown public struct ViewBlogPresenter: BlogPresenter { + + let viewRenderer: ViewRenderer - public func indexView(on container: Container, posts: [BlogPost], tags: [BlogTag], authors: [BlogUser], tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { + public func indexView(posts: [BlogPost], tags: [BlogTag], authors: [BlogUser], tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { do { - let viewRenderer = try container.make(ViewRenderer.self) let viewPosts = try posts.convertToViewBlogPosts(authors: authors, tagsForPosts: tagsForPosts, on: container) let viewTags = try tags.map { try $0.toViewBlogTag() } let context = BlogIndexPageContext(posts: viewPosts, tags: viewTags, authors: authors, pageInformation: pageInformation, paginationTagInformation: paginationTagInfo) @@ -16,10 +17,8 @@ public struct ViewBlogPresenter: BlogPresenter { } } - public func postView(on container: Container, post: BlogPost, author: BlogUser, tags: [BlogTag], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { + public func postView(post: BlogPost, author: BlogUser, tags: [BlogTag], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { do { - let viewRenderer = try container.make(ViewRenderer.self) - var postImage: String? var postImageAlt: String? if let image = try SwiftSoup.parse(markdownToHTML(post.contents)).select("img").first() { @@ -42,9 +41,8 @@ public struct ViewBlogPresenter: BlogPresenter { } - public func allAuthorsView(on container: Container, authors: [BlogUser], authorPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { + public func allAuthorsView(authors: [BlogUser], authorPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { do { - let viewRenderer = try container.make(ViewRenderer.self) var viewAuthors = try authors.map { user -> ViewBlogAuthor in guard let userID = user.userID else { throw SteamPressError(identifier: "ViewBlogPresenter", "User ID Was Not Set") @@ -60,9 +58,8 @@ public struct ViewBlogPresenter: BlogPresenter { } } - public func authorView(on container: Container, author: BlogUser, posts: [BlogPost], postCount: Int, tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { + public func authorView(author: BlogUser, posts: [BlogPost], postCount: Int, tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { do { - let viewRenderer = try container.make(ViewRenderer.self) let myProfile: Bool if let loggedInUser = pageInformation.loggedInUser { myProfile = loggedInUser.userID == author.userID @@ -77,9 +74,8 @@ public struct ViewBlogPresenter: BlogPresenter { } } - public func allTagsView(on container: Container, tags: [BlogTag], tagPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { + public func allTagsView(tags: [BlogTag], tagPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { do { - let viewRenderer = try container.make(ViewRenderer.self) var viewTags = try tags.map { tag -> BlogTagWithPostCount in guard let tagID = tag.tagID else { throw SteamPressError(identifier: "ViewBlogPresenter", "Tag ID Was Not Set") @@ -97,9 +93,8 @@ public struct ViewBlogPresenter: BlogPresenter { } } - public func tagView(on container: Container, tag: BlogTag, posts: [BlogPost], authors: [BlogUser], totalPosts: Int, pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { + public func tagView(tag: BlogTag, posts: [BlogPost], authors: [BlogUser], totalPosts: Int, pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { do { - let viewRenderer = try container.make(ViewRenderer.self) let tagsForPosts = try posts.reduce(into: [Int: [BlogTag]]()) { dict, blog in guard let blogID = blog.blogID else { throw SteamPressError(identifier: "ViewBlogPresenter", "Blog has no ID set") @@ -115,9 +110,8 @@ public struct ViewBlogPresenter: BlogPresenter { } } - public func searchView(on container: Container, totalResults: Int, posts: [BlogPost], authors: [BlogUser], searchTerm: String?, tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { + public func searchView(totalResults: Int, posts: [BlogPost], authors: [BlogUser], searchTerm: String?, tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { do { - let viewRenderer = try container.make(ViewRenderer.self) let viewPosts = try posts.convertToViewBlogPosts(authors: authors, tagsForPosts: tagsForPosts, on: container) let context = SearchPageContext(searchTerm: searchTerm, posts: viewPosts, totalResults: totalResults, pageInformation: pageInformation, paginationTagInformation: paginationTagInfo) return viewRenderer.render("blog/search", context) @@ -126,9 +120,8 @@ public struct ViewBlogPresenter: BlogPresenter { } } - public func loginView(on container: Container, loginWarning: Bool, errors: [String]?, username: String?, usernameError: Bool, passwordError: Bool, rememberMe: Bool, pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { + public func loginView(loginWarning: Bool, errors: [String]?, username: String?, usernameError: Bool, passwordError: Bool, rememberMe: Bool, pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { do { - let viewRenderer = try container.make(ViewRenderer.self) let context = LoginPageContext(errors: errors, loginWarning: loginWarning, username: username, usernameError: usernameError, passwordError: passwordError, rememberMe: rememberMe, pageInformation: pageInformation) return viewRenderer.render("blog/admin/login", context) } catch { From 1072d6971cd3b92804aaecd4e19d1730b165c99a Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Thu, 26 Mar 2020 17:31:14 +0000 Subject: [PATCH 11/70] Keep on going --- .../Controllers/Admin/LoginController.swift | 11 +++++------ .../Feed Generators/RSSFeedGenerator.swift | 13 ++++++------- .../Models/Contexts/ContextViews/ViewBlogPost.swift | 4 ++-- Sources/SteamPress/Presenters/BlogPresenter.swift | 2 +- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Sources/SteamPress/Controllers/Admin/LoginController.swift b/Sources/SteamPress/Controllers/Admin/LoginController.swift index 90acb2ee..92ea5304 100644 --- a/Sources/SteamPress/Controllers/Admin/LoginController.swift +++ b/Sources/SteamPress/Controllers/Admin/LoginController.swift @@ -59,15 +59,14 @@ struct LoginController: RouteCollection { try req.session()["SteamPressRememberMe"] = nil } - return req.blogUserRepository.getUser(username: username).flatMap { user in + return req.blogUserRepository.getUser(username: username).flatMap { user -> EventLoopFuture in let verifier = try req.make(PasswordVerifier.self) guard let user = user, try verifier.verify(password, created: user.password) else { let loginError = ["Your username or password is incorrect"] - let presenter = try req.make(BlogPresenter.self) - return try presenter.loginView(on: req, loginWarning: false, errors: loginError, username: loginData.username, usernameError: false, passwordError: false, rememberMe: loginData.rememberMe ?? false, pageInformation: req.pageInformation()).encode(for: req) + return try req.blogPresenter.loginView(loginWarning: false, errors: loginError, username: loginData.username, usernameError: false, passwordError: false, rememberMe: loginData.rememberMe ?? false, pageInformation: req.pageInformation()).encodeResponse(for: req) } try user.authenticateSession(on: req) - return req.future(req.redirect(to: self.pathCreator.createPath(for: "admin"))) + return req.eventLoop.future(req.redirect(to: self.pathCreator.createPath(for: "admin"))) } } @@ -116,7 +115,7 @@ struct LoginController: RouteCollection { guard resetPasswordErrors.isEmpty else { let view = try req.adminPresenter.createResetPasswordView(errors: resetPasswordErrors, passwordError: passwordError, confirmPasswordError: confirmPasswordError, pageInformation: req.adminPageInfomation()) - return try view.encode(for: req) + return view.encodeResponse(for: req) } let user = try req.requireAuthenticated(BlogUser.self) @@ -124,6 +123,6 @@ struct LoginController: RouteCollection { user.password = try hasher.hash(password) user.resetPasswordRequired = false let redirect = req.redirect(to: pathCreator.createPath(for: "admin")) - return req.blogUserRepository.save(user, on: req).transform(to: redirect) + return req.blogUserRepository.save(user).transform(to: redirect) } } diff --git a/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift b/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift index 93220770..d7cf3303 100644 --- a/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift +++ b/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift @@ -28,31 +28,30 @@ struct RSSFeedGenerator { // MARK: - Route Handler - func feedHandler(_ request: Request) throws -> EventLoopFuture { + func feedHandler(_ request: Request) throws -> EventLoopFuture { - let blogRepository = try request.make(BlogPostRepository.self) - return blogRepository.getAllPostsSortedByPublishDate(includeDrafts: false, on: request).flatMap { posts in - var xmlFeed = self.getXMLStart(for: request) + request.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: false).flatMap { posts in + var xmlFeed = try self.getXMLStart(for: request) if !posts.isEmpty { let postDate = posts[0].lastEdited ?? posts[0].created xmlFeed += "\(self.rfc822DateFormatter.string(from: postDate))\n" } - xmlFeed += "\nSearch \(self.title)\nSearch\n\(self.getRootPath(for: request))/search?\nterm\n\n" + xmlFeed += try "\nSearch \(self.title)\nSearch\n\(self.getRootPath(for: request))/search?\nterm\n\n" var postData: [EventLoopFuture] = [] for post in posts { try postData.append(post.getPostRSSFeed(rootPath: self.getRootPath(for: request), dateFormatter: self.rfc822DateFormatter, for: request)) } - return postData.flatten(on: request).map { postInformation in + return postData.flatten(on: request.eventLoop).map { postInformation in for post in postInformation { xmlFeed += post } xmlFeed += self.xmlEnd - var httpResponse = HTTPResponse(body: xmlFeed) + var httpResponse = Response(body: .init(stringLiteral: xmlFeed)) httpResponse.headers.add(name: .contentType, value: "application/rss+xml") return httpResponse } diff --git a/Sources/SteamPress/Models/Contexts/ContextViews/ViewBlogPost.swift b/Sources/SteamPress/Models/Contexts/ContextViews/ViewBlogPost.swift index 9081cb23..f400126c 100644 --- a/Sources/SteamPress/Models/Contexts/ContextViews/ViewBlogPost.swift +++ b/Sources/SteamPress/Models/Contexts/ContextViews/ViewBlogPost.swift @@ -94,7 +94,7 @@ extension BlogPost { } extension Array where Element: BlogPost { - func convertToViewBlogPosts(authors: [BlogUser], tagsForPosts: [Int: [BlogTag]], on container: Container) throws -> [ViewBlogPost] { + func convertToViewBlogPosts(authors: [BlogUser], tagsForPosts: [Int: [BlogTag]], on request: Request) throws -> [ViewBlogPost] { let longDateFormatter = try container.make(LongPostDateFormatter.self) let numericDateFormatter = try container.make(NumericPostDateFormatter.self) let viewPosts = try self.map { post -> ViewBlogPost in @@ -106,7 +106,7 @@ extension Array where Element: BlogPost { return viewPosts } - func convertToViewBlogPostsWithoutTags(authors: [BlogUser], on container: Container) throws -> [ViewBlogPostWithoutTags] { + func convertToViewBlogPostsWithoutTags(authors: [BlogUser], on request: Request) throws -> [ViewBlogPostWithoutTags] { let longDateFormatter = try container.make(LongPostDateFormatter.self) let numericDateFormatter = try container.make(NumericPostDateFormatter.self) let viewPosts = try self.map { post -> ViewBlogPostWithoutTags in diff --git a/Sources/SteamPress/Presenters/BlogPresenter.swift b/Sources/SteamPress/Presenters/BlogPresenter.swift index 68cf6122..ff4edb08 100644 --- a/Sources/SteamPress/Presenters/BlogPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogPresenter.swift @@ -23,7 +23,7 @@ extension Application { if let existing = self.userInfo["blogPresenter"] as? PresenterFactory { return existing } else { - let new = AdminPresenterFactory() + let new = PresenterFactory() self.userInfo["blogPresenter"] = new return new } From 0855821c449ea5d1599697695c5c79e54110759d Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 10:16:49 +0000 Subject: [PATCH 12/70] Port over password stuff to new request extensions --- .../Controllers/Admin/LoginController.swift | 7 +- .../Admin/PostAdminController.swift | 14 +- .../Admin/UserAdminController.swift | 31 ++- .../Controllers/BlogAdminController.swift | 4 +- .../Controllers/BlogController.swift | 40 ++-- .../Extensions/BCrypt+PasswordHasher.swift | 66 +++++- .../Extensions/Models+Parameters.swift | 2 +- .../SteamPress/Extensions/String+Random.swift | 5 +- .../BlogAuthSessionsMiddleware.swift | 9 +- .../BlogLoginRedirectAuthMiddleware.swift | 12 +- .../Middleware/BlogRememberMeMiddleware.swift | 14 +- Sources/SteamPress/Models/BlogUser.swift | 1 - .../Contexts/ContextViews/ViewBlogPost.swift | 8 +- .../Models/FormData/CreateUserData.swift | 18 +- .../Presenters/ViewBlogAdminPresenter.swift | 41 ++-- .../Presenters/ViewBlogPresenter.swift | 37 ++-- Sources/SteamPress/Provider.swift | 188 +++++++++--------- .../Repositories/SteamPressRepository.swift | 6 - .../Services/LongPostDateFormatter.swift | 6 +- .../Services/NumericPostFormatter.swift | 5 +- Sources/SteamPress/SteamPressError.swift | 2 +- .../AdminTests/LoginTests.swift | 1 - .../Fakes/ReversedPasswordHasher.swift | 1 - .../Helpers/TestDataBuilder.swift | 1 - .../Helpers/TestWorld+Application.swift | 1 - 25 files changed, 264 insertions(+), 256 deletions(-) diff --git a/Sources/SteamPress/Controllers/Admin/LoginController.swift b/Sources/SteamPress/Controllers/Admin/LoginController.swift index 92ea5304..ce950f5d 100644 --- a/Sources/SteamPress/Controllers/Admin/LoginController.swift +++ b/Sources/SteamPress/Controllers/Admin/LoginController.swift @@ -1,5 +1,4 @@ import Vapor -import Authentication struct LoginController: RouteCollection { @@ -60,8 +59,7 @@ struct LoginController: RouteCollection { } return req.blogUserRepository.getUser(username: username).flatMap { user -> EventLoopFuture in - let verifier = try req.make(PasswordVerifier.self) - guard let user = user, try verifier.verify(password, created: user.password) else { + guard let user = user, try req.passwordVerifier.verify(password, created: user.password) else { let loginError = ["Your username or password is incorrect"] return try req.blogPresenter.loginView(loginWarning: false, errors: loginError, username: loginData.username, usernameError: false, passwordError: false, rememberMe: loginData.rememberMe ?? false, pageInformation: req.pageInformation()).encodeResponse(for: req) } @@ -119,8 +117,7 @@ struct LoginController: RouteCollection { } let user = try req.requireAuthenticated(BlogUser.self) - let hasher = try req.make(PasswordHasher.self) - user.password = try hasher.hash(password) + user.password = try req.passwordHasher.hash(password) user.resetPasswordRequired = false let redirect = req.redirect(to: pathCreator.createPath(for: "admin")) return req.blogUserRepository.save(user).transform(to: redirect) diff --git a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift index a8304f25..207b9b2e 100644 --- a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift @@ -21,8 +21,7 @@ struct PostAdminController: RouteCollection { // MARK: - Route handlers func createPostHandler(_ req: Request) throws -> EventLoopFuture { - let presenter = try req.make(BlogAdminPresenter.self) - return try presenter.createPostView(on: req, errors: nil, title: nil, contents: nil, slugURL: nil, tags: nil, isEditing: false, post: nil, isDraft: nil, titleError: false, contentsError: false, pageInformation: req.adminPageInfomation()) + return try req.adminPresenter.createPostView(errors: nil, title: nil, contents: nil, slugURL: nil, tags: nil, isEditing: false, post: nil, isDraft: nil, titleError: false, contentsError: false, pageInformation: req.adminPageInfomation()) } func createPostPostHandler(_ req: Request) throws -> EventLoopFuture { @@ -34,8 +33,7 @@ struct PostAdminController: RouteCollection { } if let createPostErrors = validatePostCreation(data) { - let presenter = try req.make(BlogAdminPresenter.self) - let view = try presenter.createPostView(on: req, errors: createPostErrors.errors, title: data.title, contents: data.contents, slugURL: nil, tags: data.tags, isEditing: false, post: nil, isDraft: nil, titleError: createPostErrors.titleError, contentsError: createPostErrors.contentsError, pageInformation: req.adminPageInfomation()) + let view = try req.adminPresenter.createPostView(errors: createPostErrors.errors, title: data.title, contents: data.contents, slugURL: nil, tags: data.tags, isEditing: false, post: nil, isDraft: nil, titleError: createPostErrors.titleError, contentsError: createPostErrors.contentsError, pageInformation: req.adminPageInfomation()) return try view.encode(for: req) } @@ -52,7 +50,7 @@ struct PostAdminController: RouteCollection { existingTagsQuery.append(req.blogTagRepository.getTag(tagName)) } - return existingTagsQuery.flatten(on: req).flatMap { existingTagsWithOptionals in + return existingTagsQuery.flatten(on: req.eventLoop).flatMap { existingTagsWithOptionals in let existingTags = existingTagsWithOptionals.compactMap { $0 } var tagsSaves = [EventLoopFuture]() for tagName in data.tags { @@ -90,8 +88,7 @@ struct PostAdminController: RouteCollection { func editPostHandler(_ req: Request) throws -> EventLoopFuture { return try req.parameters.next(BlogPost.self).flatMap { post in return req.blogTagRepository.getTags(for: post).flatMap { tags in - let presenter = try req.make(BlogAdminPresenter.self) - return try presenter.createPostView(on: req, errors: nil, title: post.title, contents: post.contents, slugURL: post.slugUrl, tags: tags.map { $0.name }, isEditing: true, post: post, isDraft: !post.published, titleError: false, contentsError: false, pageInformation: req.adminPageInfomation()) + return try req.adminPresenter.createPostView(on: req, errors: nil, title: post.title, contents: post.contents, slugURL: post.slugUrl, tags: tags.map { $0.name }, isEditing: true, post: post, isDraft: !post.published, titleError: false, contentsError: false, pageInformation: req.adminPageInfomation()) } } } @@ -100,8 +97,7 @@ struct PostAdminController: RouteCollection { let data = try req.content.decode(CreatePostData.self) return try req.parameters.next(BlogPost.self).flatMap { post in if let errors = self.validatePostCreation(data) { - let presenter = try req.make(BlogAdminPresenter.self) - return try presenter.createPostView(on: req, errors: errors.errors, title: data.title, contents: data.contents, slugURL: post.slugUrl, tags: data.tags, isEditing: true, post: post, isDraft: !post.published, titleError: errors.titleError, contentsError: errors.contentsError, pageInformation: req.adminPageInfomation()).encode(for: req) + return try req.adminPresenter.createPostView(on: req, errors: errors.errors, title: data.title, contents: data.contents, slugURL: post.slugUrl, tags: data.tags, isEditing: true, post: post, isDraft: !post.published, titleError: errors.titleError, contentsError: errors.contentsError, pageInformation: req.adminPageInfomation()).encode(for: req) } guard let title = data.title, let contents = data.contents else { diff --git a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift index 0d0b3777..8029ef92 100644 --- a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift @@ -1,5 +1,4 @@ import Vapor -import Authentication struct UserAdminController: RouteCollection { @@ -22,8 +21,7 @@ struct UserAdminController: RouteCollection { // MARK: - Route handlers func createUserHandler(_ req: Request) throws -> EventLoopFuture { - let presenter = try req.make(BlogAdminPresenter.self) - return try presenter.createUserView(on: req, editing: false, errors: nil, name: nil, nameError: false, username: nil, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: false, userID: nil, profilePicture: nil, twitterHandle: nil, biography: nil, tagline: nil, pageInformation: req.adminPageInfomation()) + return try req.adminPresenter.createUserView(editing: false, errors: nil, name: nil, nameError: false, username: nil, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: false, userID: nil, profilePicture: nil, twitterHandle: nil, biography: nil, tagline: nil, pageInformation: req.adminPageInfomation()) } func createUserPostHandler(_ req: Request) throws -> EventLoopFuture { @@ -31,9 +29,8 @@ struct UserAdminController: RouteCollection { return try validateUserCreation(data, on: req).flatMap { createUserErrors in if let errors = createUserErrors { - let presenter = try req.make(BlogAdminPresenter.self) - let view = try presenter.createUserView(on: req, editing: false, errors: errors.errors, name: data.name, nameError: errors.nameError, username: data.username, usernameErorr: errors.usernameError, passwordError: errors.passwordError, confirmPasswordError: errors.confirmPasswordError, resetPasswordOnLogin: data.resetPasswordOnLogin ?? false, userID: nil, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline, pageInformation: req.adminPageInfomation()) - return try view.encode(for: req) + let view = try req.adminPresenter.createUserView(editing: false, errors: errors.errors, name: data.name, nameError: errors.nameError, username: data.username, usernameErorr: errors.usernameError, passwordError: errors.passwordError, confirmPasswordError: errors.confirmPasswordError, resetPasswordOnLogin: data.resetPasswordOnLogin ?? false, userID: nil, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline, pageInformation: req.adminPageInfomation()) + return view.encodeResponse(for: req) } guard let name = data.name, let username = data.username, let password = data.password else { @@ -59,8 +56,7 @@ struct UserAdminController: RouteCollection { func editUserHandler(_ req: Request) throws -> EventLoopFuture { return req.parameters.find(BlogUser.self, on: req).flatMap { user in - let presenter = try req.make(BlogAdminPresenter.self) - return try presenter.createUserView(on: req, editing: true, errors: nil, name: user.name, nameError: false, username: user.username, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: user.resetPasswordRequired, userID: user.userID, profilePicture: user.profilePicture, twitterHandle: user.twitterHandle, biography: user.biography, tagline: user.tagline, pageInformation: req.adminPageInfomation()) + return try req.adminPresenter.createUserView(editing: true, errors: nil, name: user.name, nameError: false, username: user.username, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: user.resetPasswordRequired, userID: user.userID, profilePicture: user.profilePicture, twitterHandle: user.twitterHandle, biography: user.biography, tagline: user.tagline, pageInformation: req.adminPageInfomation()) } } @@ -74,9 +70,8 @@ struct UserAdminController: RouteCollection { return try self.validateUserCreation(data, editing: true, existingUsername: user.username, on: req).flatMap { errors in if let editUserErrors = errors { - let presenter = try req.make(BlogAdminPresenter.self) - let view = try presenter.createUserView(on: req, editing: true, errors: editUserErrors.errors, name: data.name, nameError: errors?.nameError ?? false, username: data.username, usernameErorr: errors?.usernameError ?? false, passwordError: editUserErrors.passwordError, confirmPasswordError: editUserErrors.confirmPasswordError, resetPasswordOnLogin: data.resetPasswordOnLogin ?? false, userID: user.userID, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline, pageInformation: req.adminPageInfomation()) - return try view.encode(for: req) + let view = try req.adminPresenter.createUserView(editing: true, errors: editUserErrors.errors, name: data.name, nameError: errors?.nameError ?? false, username: data.username, usernameErorr: errors?.usernameError ?? false, passwordError: editUserErrors.passwordError, confirmPasswordError: editUserErrors.confirmPasswordError, resetPasswordOnLogin: data.resetPasswordOnLogin ?? false, userID: user.userID, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline, pageInformation: req.adminPageInfomation()) + return view.encodeResponse(for: req) } user.name = name @@ -111,23 +106,21 @@ struct UserAdminController: RouteCollection { req.parameters.find(BlogUser.self, on: req).and(req.blogUserRepository.getUsersCount()).flatMap { user, userCount in guard userCount > 1 else { return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: true).and(req.blogUserRepository.getAllUsers()).flatMap { posts, users in - let presenter = try req.make(BlogAdminPresenter.self) - let view = try presenter.createIndexView(on: req, posts: posts, users: users, errors: ["You cannot delete the last user"], pageInformation: req.adminPageInfomation()) - return try view.encode(for: req) + let view = try req.adminPresenter.createIndexView(posts: posts, users: users, errors: ["You cannot delete the last user"], pageInformation: req.adminPageInfomation()) + return view.encodeResponse(for: req) } } let loggedInUser = try req.requireAuthenticated(BlogUser.self) guard loggedInUser.userID != user.userID else { return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: true).and(req.blogUserRepository.getAllUsers()).flatMap { posts, users in - let presenter = try req.make(BlogAdminPresenter.self) - let view = try presenter.createIndexView(on: req, posts: posts, users: users, errors: ["You cannot delete yourself whilst logged in"], pageInformation: req.adminPageInfomation()) - return try view.encode(for: req) + let view = try req.adminPresenter.createIndexView(posts: posts, users: users, errors: ["You cannot delete yourself whilst logged in"], pageInformation: req.adminPageInfomation()) + return view.encodeResponse(for: req) } } let redirect = req.redirect(to: self.pathCreator.createPath(for: "admin")) - return userRepository.delete(user, on: req).transform(to: redirect) + return req.blogUserRepository.delete(user).transform(to: redirect) } } @@ -186,7 +179,7 @@ struct UserAdminController: RouteCollection { if editing && data.username == existingUsername { usernameUniqueError = req.eventLoop.future(nil) } else { - usernameUniqueError = req.blogUserRepository.getUser(username: username.lowercased(), on: req).map { user in + usernameUniqueError = req.blogUserRepository.getUser(username: username.lowercased()).map { user in if user != nil { return "Sorry that username has already been taken" } else { diff --git a/Sources/SteamPress/Controllers/BlogAdminController.swift b/Sources/SteamPress/Controllers/BlogAdminController.swift index 7b4c09d3..81a64c0c 100644 --- a/Sources/SteamPress/Controllers/BlogAdminController.swift +++ b/Sources/SteamPress/Controllers/BlogAdminController.swift @@ -1,5 +1,4 @@ import Vapor -import Authentication struct BlogAdminController: RouteCollection { @@ -30,8 +29,7 @@ struct BlogAdminController: RouteCollection { // MARK: Admin Handler func adminHandler(_ req: Request) throws -> EventLoopFuture { return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: true).and(req.blogUserRepository.getAllUsers()).flatMap { posts, users in - let presenter = try req.make(BlogAdminPresenter.self) - return try presenter.createIndexView(on: req, posts: posts, users: users, errors: nil, pageInformation: req.adminPageInfomation()) + return try req.adminPresenter.createIndexView(posts: posts, users: users, errors: nil, pageInformation: req.adminPageInfomation()) } } diff --git a/Sources/SteamPress/Controllers/BlogController.swift b/Sources/SteamPress/Controllers/BlogController.swift index 8a6bce24..a83ac3af 100644 --- a/Sources/SteamPress/Controllers/BlogController.swift +++ b/Sources/SteamPress/Controllers/BlogController.swift @@ -44,8 +44,7 @@ struct BlogController: RouteCollection { return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: false, count: postsPerPage, offset: paginationInformation.offset).and(req.blogTagRepository.getAllTags()).flatMap { posts, tags in req.blogUserRepository.getAllUsers().and(req.blogPostRepository.getAllPostsCount(includeDrafts: false)).flatMap { users, totalPostCount in req.blogTagRepository.getTagsForAllPosts().flatMap { tagsForPosts in - let presenter = try req.make(BlogPresenter.self) - return presenter.indexView(on: req, posts: posts, tags: tags, authors: users, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPostCount, currentQuery: req.http.url.query)) + return req.blogPresenter.indexView(posts: posts, tags: tags, authors: users, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPostCount, currentQuery: req.url.query)) } } } @@ -56,15 +55,19 @@ struct BlogController: RouteCollection { } func blogPostHandler(_ req: Request) throws -> EventLoopFuture { - guard let blogSlug = req.parameters.get("blogSlug") else { + guard let blogSlug: String = req.parameters.get("blogSlug") else { throw Abort(.badRequest) } - return req.blogPostRepository.getPost(slug: blogSlug).unwrap(or: Abort(.notFound)).flatMap { post in - let tagsQuery = req.blogTagsRepository.getTags(for: post, on: req) - let userQuery = req.blogUserRepository.getUser(id: post.author, on: req).unwrap(or: Abort(.internalServerError)) - return userQuery.and(tagsQuery).flatMap { user, tags in - let presenter = try req.make(BlogPresenter.self) - return presenter.postView(on: req, post: post, author: user, tags: tags, pageInformation: try req.pageInformation()) + return req.blogPostRepository.getPost(slug: blogSlug).unwrap(or: Abort(.notFound)).flatMap { (post: BlogPost) -> EventLoopFuture in + let tagsQuery: EventLoopFuture<[BlogTag]> = req.blogTagsRepository.getTags(for: post, on: req) + let userQuery: EventLoopFuture = req.blogUserRepository.getUser(id: post.author, on: req).unwrap(or: Abort(.internalServerError)) + return userQuery.and(tagsQuery).flatMap { (user: BlogUser, tags: [BlogTag]) -> EventLoopFuture in + do { + let pageInformation: BlogGlobalPageInformation = try req.pageInformation() + return req.blogPresenter.postView(post: post, author: user, tags: tags, pageInformation: pageInformation) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } } } @@ -76,9 +79,8 @@ struct BlogController: RouteCollection { let postCountQuery = req.blogPostRepository.getPublishedPostCount(for: tag) let usersQuery = req.blogUsersRepository.getAllUsers() return flatMap(postsQuery, postCountQuery, usersQuery) { posts, totalPosts, authors in - let presenter = try req.make(BlogPresenter.self) let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPosts, currentQuery: req.http.url.query) - return presenter.tagView(on: req, tag: tag, posts: posts, authors: authors, totalPosts: totalPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + return req.blogPresenter.tagView(tag: tag, posts: posts, authors: authors, totalPosts: totalPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) } } } @@ -97,9 +99,8 @@ struct BlogController: RouteCollection { let authorPostCountQuery = req.blogPostRepository.getPostCount(for: author) return authorPostQuery.and(authorPostCountQuery).flatMap { posts, postCount in tagQuery.flatMap { tagsForPosts in - let presenter = try req.make(BlogPresenter.self) - let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: postCount, currentQuery: req.http.url.query) - return presenter.authorView(on: req, author: author, posts: posts, postCount: postCount, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: postCount, currentQuery: req.url.query) + return req.blogPresenter.authorView(author: author, posts: posts, postCount: postCount, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) } } } @@ -107,7 +108,6 @@ struct BlogController: RouteCollection { func allTagsViewHandler(_ req: Request) throws -> EventLoopFuture { return req.blogTagRepository.getAllTagsWithPostCount().flatMap { tagswithCount in - let presenter = try req.make(BlogPresenter.self) let allTags = tagswithCount.map { $0.0 } let tagCounts = try tagswithCount.reduce(into: [Int: Int]()) { guard let tagID = $1.0.tagID else { @@ -115,12 +115,11 @@ struct BlogController: RouteCollection { } return $0[tagID] = $1.1 } - return presenter.allTagsView(on: req, tags: allTags, tagPostCounts: tagCounts, pageInformation: try req.pageInformation()) + return req.blogPresenter.allTagsView(tags: allTags, tagPostCounts: tagCounts, pageInformation: try req.pageInformation()) } } func allAuthorsViewHandler(_ req: Request) throws -> EventLoopFuture { - let presenter = try req.make(BlogPresenter.self) return req.blogUserRepository.getAllUsersWithPostCount().flatMap { allUsersWithCount in let allUsers = allUsersWithCount.map { $0.0 } let authorCounts = try allUsersWithCount.reduce(into: [Int: Int]()) { @@ -129,16 +128,15 @@ struct BlogController: RouteCollection { } return $0[userID] = $1.1 } - return presenter.allAuthorsView(on: req, authors: allUsers, authorPostCounts: authorCounts, pageInformation: try req.pageInformation()) + return req.blogPresenter.allAuthorsView(authors: allUsers, authorPostCounts: authorCounts, pageInformation: try req.pageInformation()) } } func searchHandler(_ req: Request) throws -> EventLoopFuture { - let preseneter = try req.make(BlogPresenter.self) let paginationInformation = req.getPaginationInformation(postsPerPage: postsPerPage) guard let searchTerm = req.query[String.self, at: "term"], !searchTerm.isEmpty else { let paginationTagInfo = getPaginationInformation(currentPage: paginationInformation.page, totalPosts: 0, currentQuery: req.url.query) - return preseneter.searchView(on: req, totalResults: 0, posts: [], authors: [], searchTerm: nil, tagsForPosts: [:], pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + return req.blogPresenter.searchView(totalResults: 0, posts: [], authors: [], searchTerm: nil, tagsForPosts: [:], pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) } let postsCountQuery = req.blogPostRepository.getPublishedPostCount(for: searchTerm) @@ -148,7 +146,7 @@ struct BlogController: RouteCollection { return postsQuery.and(postsCountQuery).flatMap { posts, totalPosts in userQuery.and(tagsQuery).flatMap { users, tagsForPosts in let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPosts, currentQuery: req.url.query) - return preseneter.searchView(on: req, totalResults: totalPosts, posts: posts, authors: users, searchTerm: searchTerm, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + return req.blogPresenter.searchView(totalResults: totalPosts, posts: posts, authors: users, searchTerm: searchTerm, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) } } } diff --git a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift index f9b94eed..fdb2b1b1 100644 --- a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift +++ b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift @@ -1,12 +1,70 @@ import Vapor import Crypto -public protocol PasswordHasher: Service { - func hash(_ plaintext: LosslessDataConvertible) throws -> String +public protocol PasswordHasher { + func hash(_ plaintext: String) throws -> String } extension BCryptDigest: PasswordHasher { - public func hash(_ plaintext: LosslessDataConvertible) throws -> String { - return try self.hash(plaintext, salt: nil) + public func hash(_ plaintext: String) throws -> String { + return try self.hash(plaintext) + } +} + + +public extension Request { + var passwordHasher: PasswordHasher { + self.application.passwordHashers.makeHasher!(self) + } + + var passwordVerifier: PasswordVerifier { + self.application.passwordVerifiers.makeVerifier!(self) + } +} + +private extension Application { + var passwordHashers: PasswordHasherFactory { + get { + if let existing = self.userInfo["passwordHasher"] as? PasswordHasherFactory { + return existing + } else { + let new = PasswordHasherFactory() + self.userInfo["passwordHasher"] = new + return new + } + } + set { + self.userInfo["passwordHasher"] = newValue + } + } + + var passwordVerifiers: PasswordVerifierFactory { + get { + if let existing = self.userInfo["passwordVerifier"] as? PasswordVerifierFactory { + return existing + } else { + let new = PasswordVerifierFactory() + self.userInfo["passwordVerifier"] = new + return new + } + } + set { + self.userInfo["passwordVerifier"] = newValue + } + } + +} + +private struct PasswordHasherFactory { + var makeHasher: ((Request) -> PasswordHasher)? + mutating func use(_ makeHasher: @escaping (Request) -> PasswordHasher) { + self.makeHasher = makeHasher + } +} + +private struct PasswordVerifierFactory { + var makeVerifier: ((Request) -> PasswordVerifier)? + mutating func use(_ makeVerifier: @escaping (Request) -> PasswordVerifier) { + self.makeVerifier = makeVerifier } } diff --git a/Sources/SteamPress/Extensions/Models+Parameters.swift b/Sources/SteamPress/Extensions/Models+Parameters.swift index e3080bb5..c001d935 100644 --- a/Sources/SteamPress/Extensions/Models+Parameters.swift +++ b/Sources/SteamPress/Extensions/Models+Parameters.swift @@ -62,6 +62,6 @@ extension Parameters { guard let idString = req.parameters.get(T.parameterKey), let id = Int(idString) else { return req.eventLoop.makeFailedFuture(Abort(.badRequest)) } - return req.blogUserRepository.getUser(id: id, on: req).unwrap(or: Abort(.notFound)) + return req.blogUserRepository.getUser(id: id).unwrap(or: Abort(.notFound)) } } diff --git a/Sources/SteamPress/Extensions/String+Random.swift b/Sources/SteamPress/Extensions/String+Random.swift index 71095523..f7e25d6a 100644 --- a/Sources/SteamPress/Extensions/String+Random.swift +++ b/Sources/SteamPress/Extensions/String+Random.swift @@ -1,9 +1,8 @@ import Crypto extension String { - public static func random(length: Int = 12) throws -> String { - let randomData = try CryptoRandom().generateData(count: length) - let randomString = randomData.base64EncodedString() + public static func random(length: Int = 24) throws -> String { + let randomString = [UInt8].random(count: length).base64 return randomString } } diff --git a/Sources/SteamPress/Middleware/BlogAuthSessionsMiddleware.swift b/Sources/SteamPress/Middleware/BlogAuthSessionsMiddleware.swift index b739b45e..f8314bbf 100644 --- a/Sources/SteamPress/Middleware/BlogAuthSessionsMiddleware.swift +++ b/Sources/SteamPress/Middleware/BlogAuthSessionsMiddleware.swift @@ -4,22 +4,21 @@ public final class BlogAuthSessionsMiddleware: Middleware { public init() {} - public func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture { + public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { let future: EventLoopFuture if let userIDString = try request.session()["_BlogUserSession"], let userID = Int(userIDString) { - let userRepository = try request.make(BlogUserRepository.self) - future = userRepository.getUser(id: userID, on: request).flatMap { user in + future = request.blogUserRepository.getUser(id: userID).flatMap { user in if let user = user { try request.authenticate(user) } return .done(on: request) } } else { - future = .done(on: request) + future = .done(on: request.eventLoop) } return future.flatMap { - return try next.respond(to: request).map { response in + return next.respond(to: request).map { response in if let user = try request.authenticated(BlogUser.self) { try user.authenticateSession(on: request) } else { diff --git a/Sources/SteamPress/Middleware/BlogLoginRedirectAuthMiddleware.swift b/Sources/SteamPress/Middleware/BlogLoginRedirectAuthMiddleware.swift index eb78d457..21b7082e 100644 --- a/Sources/SteamPress/Middleware/BlogLoginRedirectAuthMiddleware.swift +++ b/Sources/SteamPress/Middleware/BlogLoginRedirectAuthMiddleware.swift @@ -3,22 +3,22 @@ import Vapor struct BlogLoginRedirectAuthMiddleware: Middleware { let pathCreator: BlogPathCreator - - func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture { + + func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { do { let user = try request.requireAuthenticated(BlogUser.self) let resetPasswordPath = pathCreator.createPath(for: "admin/resetPassword") - var requestPath = request.http.urlString + var requestPath = request.url.string if !requestPath.hasSuffix("/") { requestPath = requestPath + "/" } if user.resetPasswordRequired && requestPath != resetPasswordPath { let redirect = request.redirect(to: resetPasswordPath) - return request.future(redirect) + return request.eventLoop.future(redirect) } } catch { - return request.future(request.redirect(to: pathCreator.createPath(for: "admin/login", query: "loginRequired"))) + return request.eventLoop.future(request.redirect(to: pathCreator.createPath(for: "admin/login", query: "loginRequired"))) } - return try next.respond(to: request) + return next.respond(to: request) } } diff --git a/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift b/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift index 8ee207d7..0f6478de 100644 --- a/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift +++ b/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift @@ -1,18 +1,14 @@ import Vapor -public struct BlogRememberMeMiddleware: Middleware, ServiceType { +public struct BlogRememberMeMiddleware: Middleware { - public static func makeService(for container: Container) throws -> BlogRememberMeMiddleware { - return .init() - } - - public func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture { - return try next.respond(to: request).map { response in + public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { + return next.respond(to: request).flatMapThrowing { response in if let rememberMe = try request.session()["SteamPressRememberMe"], rememberMe == "YES" { - if var steampressCookie = response.http.cookies["steampress-session"] { + if var steampressCookie = response.cookies["steampress-session"] { let oneYear: TimeInterval = 60 * 60 * 24 * 365 steampressCookie.expires = Date().addingTimeInterval(oneYear) - response.http.cookies["steampress-session"] = steampressCookie + response.cookies["steampress-session"] = steampressCookie try request.session()["SteamPressRememberMe"] = nil } } diff --git a/Sources/SteamPress/Models/BlogUser.swift b/Sources/SteamPress/Models/BlogUser.swift index 3d9cb11d..f67808bf 100644 --- a/Sources/SteamPress/Models/BlogUser.swift +++ b/Sources/SteamPress/Models/BlogUser.swift @@ -1,5 +1,4 @@ import Vapor -import Authentication // MARK: - Model diff --git a/Sources/SteamPress/Models/Contexts/ContextViews/ViewBlogPost.swift b/Sources/SteamPress/Models/Contexts/ContextViews/ViewBlogPost.swift index f400126c..7c11a29c 100644 --- a/Sources/SteamPress/Models/Contexts/ContextViews/ViewBlogPost.swift +++ b/Sources/SteamPress/Models/Contexts/ContextViews/ViewBlogPost.swift @@ -94,9 +94,7 @@ extension BlogPost { } extension Array where Element: BlogPost { - func convertToViewBlogPosts(authors: [BlogUser], tagsForPosts: [Int: [BlogTag]], on request: Request) throws -> [ViewBlogPost] { - let longDateFormatter = try container.make(LongPostDateFormatter.self) - let numericDateFormatter = try container.make(NumericPostDateFormatter.self) + func convertToViewBlogPosts(authors: [BlogUser], tagsForPosts: [Int: [BlogTag]], longDateFormatter: LongPostDateFormatter, numericDateFormatter: NumericPostDateFormatter) throws -> [ViewBlogPost] { let viewPosts = try self.map { post -> ViewBlogPost in guard let blogID = post.blogID else { throw SteamPressError(identifier: "ViewBlogPost", "Post has no ID set") @@ -106,9 +104,7 @@ extension Array where Element: BlogPost { return viewPosts } - func convertToViewBlogPostsWithoutTags(authors: [BlogUser], on request: Request) throws -> [ViewBlogPostWithoutTags] { - let longDateFormatter = try container.make(LongPostDateFormatter.self) - let numericDateFormatter = try container.make(NumericPostDateFormatter.self) + func convertToViewBlogPostsWithoutTags(authors: [BlogUser], longDateFormatter: LongPostDateFormatter, numericDateFormatter: NumericPostDateFormatter) throws -> [ViewBlogPostWithoutTags] { let viewPosts = try self.map { post -> ViewBlogPostWithoutTags in return try post.toViewPostWithoutTags(authorName: authors.getAuthorName(id: post.author), authorUsername: authors.getAuthorUsername(id: post.author), longFormatter: longDateFormatter, numericFormatter: numericDateFormatter) } diff --git a/Sources/SteamPress/Models/FormData/CreateUserData.swift b/Sources/SteamPress/Models/FormData/CreateUserData.swift index e8705ac0..94679e41 100644 --- a/Sources/SteamPress/Models/FormData/CreateUserData.swift +++ b/Sources/SteamPress/Models/FormData/CreateUserData.swift @@ -12,12 +12,16 @@ struct CreateUserData: Content { let resetPasswordOnLogin: Bool? } -extension CreateUserData: Validatable, Reflectable { - static func validations() throws -> Validations { - var validations = Validations(CreateUserData.self) - let usernameCharacterSet = CharacterSet(charactersIn: "-_") - let usernameValidationCharacters = Validator.characterSet(.alphanumerics + usernameCharacterSet) - try validations.add(\.username, usernameValidationCharacters || .nil) - return validations +extension CreateUserData: Validatable { +// static func validations() throws -> Validations { +// var validations = Validations(CreateUserData.self) +// let usernameCharacterSet = CharacterSet(charactersIn: "-_") +// let usernameValidationCharacters = Validator.characterSet(.alphanumerics + usernameCharacterSet) +// try validations.add(\.username, usernameValidationCharacters || .nil) +// return validations +// } + + static func validations(_ validations: inout Validations) { + #warning("TODO") } } diff --git a/Sources/SteamPress/Presenters/ViewBlogAdminPresenter.swift b/Sources/SteamPress/Presenters/ViewBlogAdminPresenter.swift index bf33d92a..7c3df1e7 100644 --- a/Sources/SteamPress/Presenters/ViewBlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/ViewBlogAdminPresenter.swift @@ -4,57 +4,52 @@ public struct ViewBlogAdminPresenter: BlogAdminPresenter { let pathCreator: BlogPathCreator let viewRenderer: ViewRenderer + let eventLoopGroup: EventLoopGroup + let longDateFormatter: LongPostDateFormatter + let numericDateFormatter: NumericPostDateFormatter public func createIndexView(posts: [BlogPost], users: [BlogUser], errors: [String]?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { do { - let publishedPosts = try posts.filter { $0.published }.convertToViewBlogPostsWithoutTags(authors: users, on: container) - let draftPosts = try posts.filter { !$0.published }.convertToViewBlogPostsWithoutTags(authors: users, on: container) + let publishedPosts = try posts.filter { $0.published }.convertToViewBlogPostsWithoutTags(authors: users, longDateFormatter: longDateFormatter, numericDateFormatter: numericDateFormatter) + let draftPosts = try posts.filter { !$0.published }.convertToViewBlogPostsWithoutTags(authors: users, longDateFormatter: longDateFormatter, numericDateFormatter: numericDateFormatter) let context = AdminPageContext(errors: errors, publishedPosts: publishedPosts, draftPosts: draftPosts, users: users, pageInformation: pageInformation) return viewRenderer.render("blog/admin/index", context) } catch { - return container.future(error: error) + return eventLoopGroup.future(error: error) } } public func createPostView(errors: [String]?, title: String?, contents: String?, slugURL: String?, tags: [String]?, isEditing: Bool, post: BlogPost?, isDraft: Bool?, titleError: Bool, contentsError: Bool, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { - do { - if isEditing { - guard post != nil else { - return container.future(error: SteamPressError(identifier: "ViewBlogAdminPresenter", "Blog Post is empty whilst editing")) - } + if isEditing { + guard post != nil else { + return eventLoopGroup.future(error: SteamPressError(identifier: "ViewBlogAdminPresenter", "Blog Post is empty whilst editing")) } - let postPathSuffix = pathCreator.createPath(for: "posts") - let postPathPrefix = pageInformation.websiteURL.appendingPathComponent(postPathSuffix) - let pageTitle = isEditing ? "Edit Blog Post" : "Create Blog Post" - let context = CreatePostPageContext(title: pageTitle, editing: isEditing, post: post, draft: isDraft ?? false, errors: errors, titleSupplied: title, contentsSupplied: contents, tagsSupplied: tags, slugURLSupplied: slugURL, titleError: titleError, contentsError: contentsError, postPathPrefix: postPathPrefix.absoluteString, pageInformation: pageInformation) - return viewRenderer.render("blog/admin/createPost", context) - } catch { - return container.future(error: error) } + let postPathSuffix = pathCreator.createPath(for: "posts") + let postPathPrefix = pageInformation.websiteURL.appendingPathComponent(postPathSuffix) + let pageTitle = isEditing ? "Edit Blog Post" : "Create Blog Post" + let context = CreatePostPageContext(title: pageTitle, editing: isEditing, post: post, draft: isDraft ?? false, errors: errors, titleSupplied: title, contentsSupplied: contents, tagsSupplied: tags, slugURLSupplied: slugURL, titleError: titleError, contentsError: contentsError, postPathPrefix: postPathPrefix.absoluteString, pageInformation: pageInformation) + return viewRenderer.render("blog/admin/createPost", context) } public func createUserView(editing: Bool, errors: [String]?, name: String?, nameError: Bool, username: String?, usernameErorr: Bool, passwordError: Bool, confirmPasswordError: Bool, resetPasswordOnLogin: Bool, userID: Int?, profilePicture: String?, twitterHandle: String?, biography: String?, tagline: String?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { do { if editing { guard userID != nil else { - return container.future(error: SteamPressError(identifier: "ViewBlogAdminPresenter", "User ID is nil whilst editing")) + return eventLoopGroup.future(error: SteamPressError(identifier: "ViewBlogAdminPresenter", "User ID is nil whilst editing")) } } let context = CreateUserPageContext(editing: editing, errors: errors, nameSupplied: name, nameError: nameError, usernameSupplied: username, usernameError: usernameErorr, passwordError: passwordError, confirmPasswordError: confirmPasswordError, resetPasswordOnLoginSupplied: resetPasswordOnLogin, userID: userID, twitterHandleSupplied: twitterHandle, profilePictureSupplied: profilePicture, biographySupplied: biography, taglineSupplied: tagline, pageInformation: pageInformation) return viewRenderer.render("blog/admin/createUser", context) } catch { - return container.future(error: error) + return eventLoopGroup.future(error: error) } } public func createResetPasswordView(errors: [String]?, passwordError: Bool?, confirmPasswordError: Bool?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { - do { - let context = ResetPasswordPageContext(errors: errors, passwordError: passwordError, confirmPasswordError: confirmPasswordError, pageInformation: pageInformation) - return viewRenderer.render("blog/admin/resetPassword", context) - } catch { - return container.future(error: error) - } + let context = ResetPasswordPageContext(errors: errors, passwordError: passwordError, confirmPasswordError: confirmPasswordError, pageInformation: pageInformation) + return viewRenderer.render("blog/admin/resetPassword", context) } } diff --git a/Sources/SteamPress/Presenters/ViewBlogPresenter.swift b/Sources/SteamPress/Presenters/ViewBlogPresenter.swift index 11ca7d12..fef8a8c7 100644 --- a/Sources/SteamPress/Presenters/ViewBlogPresenter.swift +++ b/Sources/SteamPress/Presenters/ViewBlogPresenter.swift @@ -5,15 +5,18 @@ import SwiftMarkdown public struct ViewBlogPresenter: BlogPresenter { let viewRenderer: ViewRenderer + let longDateFormatter: LongPostDateFormatter + let numericDateFormatter: NumericPostDateFormatter + let eventLoopGroup: EventLoopGroup public func indexView(posts: [BlogPost], tags: [BlogTag], authors: [BlogUser], tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { do { - let viewPosts = try posts.convertToViewBlogPosts(authors: authors, tagsForPosts: tagsForPosts, on: container) + let viewPosts = try posts.convertToViewBlogPosts(authors: authors, tagsForPosts: tagsForPosts, longDateFormatter: longDateFormatter, numericDateFormatter: numericDateFormatter) let viewTags = try tags.map { try $0.toViewBlogTag() } let context = BlogIndexPageContext(posts: viewPosts, tags: viewTags, authors: authors, pageInformation: pageInformation, paginationTagInformation: paginationTagInfo) return viewRenderer.render("blog/blog", context) } catch { - return container.future(error: error) + return eventLoopGroup.future(error: error) } } @@ -29,14 +32,12 @@ public struct ViewBlogPresenter: BlogPresenter { } } let shortSnippet = post.shortSnippet() - let longFormatter = try container.make(LongPostDateFormatter.self) - let numericFormatter = try container.make(NumericPostDateFormatter.self) - let viewPost = try post.toViewPost(authorName: author.name, authorUsername: author.username, longFormatter: longFormatter, numericFormatter: numericFormatter, tags: tags) + let viewPost = try post.toViewPost(authorName: author.name, authorUsername: author.username, longFormatter: longDateFormatter, numericFormatter: numericDateFormatter, tags: tags) let context = BlogPostPageContext(title: post.title, post: viewPost, author: author, pageInformation: pageInformation, postImage: postImage, postImageAlt: postImageAlt, shortSnippet: shortSnippet) return viewRenderer.render("blog/post", context) } catch { - return container.future(error: error) + return eventLoopGroup.future(error: error) } } @@ -54,7 +55,7 @@ public struct ViewBlogPresenter: BlogPresenter { let context = AllAuthorsPageContext(pageInformation: pageInformation, authors: viewAuthors) return viewRenderer.render("blog/authors", context) } catch { - return container.future(error: error) + return eventLoopGroup.future(error: error) } } @@ -66,11 +67,11 @@ public struct ViewBlogPresenter: BlogPresenter { } else { myProfile = false } - let viewPosts = try posts.convertToViewBlogPosts(authors: [author], tagsForPosts: tagsForPosts, on: container) + let viewPosts = try posts.convertToViewBlogPosts(authors: [author], tagsForPosts: tagsForPosts, longDateFormatter: longDateFormatter, numericDateFormatter: numericDateFormatter) let context = AuthorPageContext(author: author, posts: viewPosts, pageInformation: pageInformation, myProfile: myProfile, postCount: postCount, paginationTagInformation: paginationTagInfo) return viewRenderer.render("blog/profile", context) } catch { - return container.future(error: error) + return eventLoopGroup.future(error: error) } } @@ -89,7 +90,7 @@ public struct ViewBlogPresenter: BlogPresenter { let context = AllTagsPageContext(title: "All Tags", tags: viewTags, pageInformation: pageInformation) return viewRenderer.render("blog/tags", context) } catch { - return container.future(error: error) + return eventLoopGroup.future(error: error) } } @@ -102,31 +103,27 @@ public struct ViewBlogPresenter: BlogPresenter { dict[blogID] = [tag] } - let viewPosts = try posts.convertToViewBlogPosts(authors: authors, tagsForPosts: tagsForPosts, on: container) + let viewPosts = try posts.convertToViewBlogPosts(authors: authors, tagsForPosts: tagsForPosts, longDateFormatter: longDateFormatter, numericDateFormatter: numericDateFormatter) let context = TagPageContext(tag: tag, pageInformation: pageInformation, posts: viewPosts, postCount: totalPosts, paginationTagInformation: paginationTagInfo) return viewRenderer.render("blog/tag", context) } catch { - return container.future(error: error) + return eventLoopGroup.future(error: error) } } public func searchView(totalResults: Int, posts: [BlogPost], authors: [BlogUser], searchTerm: String?, tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { do { - let viewPosts = try posts.convertToViewBlogPosts(authors: authors, tagsForPosts: tagsForPosts, on: container) + let viewPosts = try posts.convertToViewBlogPosts(authors: authors, tagsForPosts: tagsForPosts, longDateFormatter: longDateFormatter, numericDateFormatter: numericDateFormatter) let context = SearchPageContext(searchTerm: searchTerm, posts: viewPosts, totalResults: totalResults, pageInformation: pageInformation, paginationTagInformation: paginationTagInfo) return viewRenderer.render("blog/search", context) } catch { - return container.future(error: error) + return eventLoopGroup.future(error: error) } } public func loginView(loginWarning: Bool, errors: [String]?, username: String?, usernameError: Bool, passwordError: Bool, rememberMe: Bool, pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { - do { - let context = LoginPageContext(errors: errors, loginWarning: loginWarning, username: username, usernameError: usernameError, passwordError: passwordError, rememberMe: rememberMe, pageInformation: pageInformation) - return viewRenderer.render("blog/admin/login", context) - } catch { - return container.future(error: error) - } + let context = LoginPageContext(errors: errors, loginWarning: loginWarning, username: username, usernameError: usernameError, passwordError: passwordError, rememberMe: rememberMe, pageInformation: pageInformation) + return viewRenderer.render("blog/admin/login", context) } } diff --git a/Sources/SteamPress/Provider.swift b/Sources/SteamPress/Provider.swift index 3db47864..7da21fde 100644 --- a/Sources/SteamPress/Provider.swift +++ b/Sources/SteamPress/Provider.swift @@ -1,94 +1,94 @@ -import Vapor -import Authentication - -public struct Provider: Vapor.Provider { - - let blogPath: String? - let feedInformation: FeedInformation - let postsPerPage: Int - let enableAuthorPages: Bool - let enableTagPages: Bool - let pathCreator: BlogPathCreator - - /** - Initialiser for SteamPress' Provider to add a blog to your Vapor App. You can pass it an optional - `blogPath` to add the blog to. For instance, if you pass in "blog", your blog will be accessible - at http://mysite.com/blog/, or if you pass in `nil` your blog will be added to the root of your - site (i.e. http://mysite.com/) - - Parameter blogPath: The path to add the blog to (see above). - - Parameter feedInformation: Information to vend to the RSS and Atom feeds. Defaults to empty information. - - Parameter postsPerPage: The number of posts to show per page on the main index page of the blog. Defaults to 10. - - Parameter enableAuthorsPages: Flag used to determine whether to publicly expose the authors endpoints - or not. Defaults to true. - - Parameter enableTagsPages: Flag used to determine whether to publicy expose the tags endpoints or not. - Defaults to true. - */ - public init( - blogPath: String? = nil, - feedInformation: FeedInformation = FeedInformation(), - postsPerPage: Int = 10, - enableAuthorPages: Bool = true, - enableTagPages: Bool = true) { - self.blogPath = blogPath - self.feedInformation = feedInformation - self.postsPerPage = postsPerPage - self.enableAuthorPages = enableAuthorPages - self.enableTagPages = enableTagPages - self.pathCreator = BlogPathCreator(blogPath: self.blogPath) - } - - public func register(_ services: inout Services) throws { - services.register(BlogPresenter.self) { _ in - return ViewBlogPresenter() - } - - services.register(BlogAdminPresenter.self) { _ in - return ViewBlogAdminPresenter(pathCreator: self.pathCreator) - } - - try services.register(AuthenticationProvider()) - services.register([PasswordHasher.self, PasswordVerifier.self]) { _ in - return BCryptDigest() - } - services.register(SteamPressRandomNumberGenerator.self) { _ in - return RealRandomNumberGenerator() - } - - services.register(BlogRememberMeMiddleware.self) - services.register(LongPostDateFormatter.self) - services.register(NumericPostDateFormatter.self) - } - - public func willBoot(_ container: Container) throws -> EventLoopFuture { - let router = try container.make(Router.self) - - let feedController = FeedController(pathCreator: self.pathCreator, feedInformation: self.feedInformation) - let apiController = APIController() - let blogController = BlogController(pathCreator: self.pathCreator, enableAuthorPages: self.enableAuthorPages, enableTagPages: self.enableTagPages, postsPerPage: self.postsPerPage) - let blogAdminController = BlogAdminController(pathCreator: self.pathCreator) - - let blogRoutes: RoutesBuilder - if let blogPath = blogPath { - blogRoutes = router.grouped(blogPath) - } else { - blogRoutes = router.grouped("") - } - let steampressSessionsConfig = SessionsConfig(cookieName: "steampress-session") { value in - return HTTPCookieValue(string: value) - } - let steampressSessions = try SessionsMiddleware(sessions: container.make(), config: steampressSessionsConfig) - let steampressAuthSessions = BlogAuthSessionsMiddleware() - let sessionedRoutes = blogRoutes.grouped(steampressSessions, steampressAuthSessions) - - try sessionedRoutes.register(collection: feedController) - try sessionedRoutes.register(collection: apiController) - try sessionedRoutes.register(collection: blogController) - try sessionedRoutes.register(collection: blogAdminController) - return .done(on: container) - } - - public func didBoot(_ container: Container) throws -> EventLoopFuture { - return .done(on: container) - } - -} +//import Vapor +//import Authentication +// +//public struct Provider: Vapor.Provider { +// +// let blogPath: String? +// let feedInformation: FeedInformation +// let postsPerPage: Int +// let enableAuthorPages: Bool +// let enableTagPages: Bool +// let pathCreator: BlogPathCreator +// +// /** +// Initialiser for SteamPress' Provider to add a blog to your Vapor App. You can pass it an optional +// `blogPath` to add the blog to. For instance, if you pass in "blog", your blog will be accessible +// at http://mysite.com/blog/, or if you pass in `nil` your blog will be added to the root of your +// site (i.e. http://mysite.com/) +// - Parameter blogPath: The path to add the blog to (see above). +// - Parameter feedInformation: Information to vend to the RSS and Atom feeds. Defaults to empty information. +// - Parameter postsPerPage: The number of posts to show per page on the main index page of the blog. Defaults to 10. +// - Parameter enableAuthorsPages: Flag used to determine whether to publicly expose the authors endpoints +// or not. Defaults to true. +// - Parameter enableTagsPages: Flag used to determine whether to publicy expose the tags endpoints or not. +// Defaults to true. +// */ +// public init( +// blogPath: String? = nil, +// feedInformation: FeedInformation = FeedInformation(), +// postsPerPage: Int = 10, +// enableAuthorPages: Bool = true, +// enableTagPages: Bool = true) { +// self.blogPath = blogPath +// self.feedInformation = feedInformation +// self.postsPerPage = postsPerPage +// self.enableAuthorPages = enableAuthorPages +// self.enableTagPages = enableTagPages +// self.pathCreator = BlogPathCreator(blogPath: self.blogPath) +// } +// +// public func register(_ services: inout Services) throws { +// services.register(BlogPresenter.self) { _ in +// return ViewBlogPresenter() +// } +// +// services.register(BlogAdminPresenter.self) { _ in +// return ViewBlogAdminPresenter(pathCreator: self.pathCreator) +// } +// +// try services.register(AuthenticationProvider()) +// services.register([PasswordHasher.self, PasswordVerifier.self]) { _ in +// return BCryptDigest() +// } +// services.register(SteamPressRandomNumberGenerator.self) { _ in +// return RealRandomNumberGenerator() +// } +// +// services.register(BlogRememberMeMiddleware.self) +// services.register(LongPostDateFormatter.self) +// services.register(NumericPostDateFormatter.self) +// } +// +// public func willBoot(_ container: Container) throws -> EventLoopFuture { +// let router = try container.make(Router.self) +// +// let feedController = FeedController(pathCreator: self.pathCreator, feedInformation: self.feedInformation) +// let apiController = APIController() +// let blogController = BlogController(pathCreator: self.pathCreator, enableAuthorPages: self.enableAuthorPages, enableTagPages: self.enableTagPages, postsPerPage: self.postsPerPage) +// let blogAdminController = BlogAdminController(pathCreator: self.pathCreator) +// +// let blogRoutes: RoutesBuilder +// if let blogPath = blogPath { +// blogRoutes = router.grouped(blogPath) +// } else { +// blogRoutes = router.grouped("") +// } +// let steampressSessionsConfig = SessionsConfig(cookieName: "steampress-session") { value in +// return HTTPCookieValue(string: value) +// } +// let steampressSessions = try SessionsMiddleware(sessions: container.make(), config: steampressSessionsConfig) +// let steampressAuthSessions = BlogAuthSessionsMiddleware() +// let sessionedRoutes = blogRoutes.grouped(steampressSessions, steampressAuthSessions) +// +// try sessionedRoutes.register(collection: feedController) +// try sessionedRoutes.register(collection: apiController) +// try sessionedRoutes.register(collection: blogController) +// try sessionedRoutes.register(collection: blogAdminController) +// return .done(on: container) +// } +// +// public func didBoot(_ container: Container) throws -> EventLoopFuture { +// return .done(on: container) +// } +// +//} diff --git a/Sources/SteamPress/Repositories/SteamPressRepository.swift b/Sources/SteamPress/Repositories/SteamPressRepository.swift index 9adf76b6..f5870e71 100644 --- a/Sources/SteamPress/Repositories/SteamPressRepository.swift +++ b/Sources/SteamPress/Repositories/SteamPressRepository.swift @@ -46,12 +46,6 @@ public protocol BlogUserRepository: SteamPressRepository { func getUsersCount() -> EventLoopFuture } -//extension Request { -// var blogUserRepository: BlogUserRepository { -// -// } -//} - public extension Request { var blogUserRepository: BlogUserRepository { self.application.blogUserRepositories.makeRepository!(self) diff --git a/Sources/SteamPress/Services/LongPostDateFormatter.swift b/Sources/SteamPress/Services/LongPostDateFormatter.swift index 1092462b..04e8131f 100644 --- a/Sources/SteamPress/Services/LongPostDateFormatter.swift +++ b/Sources/SteamPress/Services/LongPostDateFormatter.swift @@ -1,11 +1,7 @@ import Foundation import Vapor -struct LongPostDateFormatter: ServiceType { - static func makeService(for container: Container) throws -> LongPostDateFormatter { - return .init() - } - +struct LongPostDateFormatter { let formatter: DateFormatter init() { diff --git a/Sources/SteamPress/Services/NumericPostFormatter.swift b/Sources/SteamPress/Services/NumericPostFormatter.swift index 578a5d2b..0271dec7 100644 --- a/Sources/SteamPress/Services/NumericPostFormatter.swift +++ b/Sources/SteamPress/Services/NumericPostFormatter.swift @@ -1,10 +1,7 @@ import Foundation import Vapor -struct NumericPostDateFormatter: ServiceType { - static func makeService(for container: Container) throws -> NumericPostDateFormatter { - return .init() - } +struct NumericPostDateFormatter { let formatter: DateFormatter diff --git a/Sources/SteamPress/SteamPressError.swift b/Sources/SteamPress/SteamPressError.swift index ff396d6d..7e1a4712 100644 --- a/Sources/SteamPress/SteamPressError.swift +++ b/Sources/SteamPress/SteamPressError.swift @@ -1,6 +1,6 @@ import Vapor -struct SteamPressError: AbortError, Debuggable { +struct SteamPressError: AbortError, DebuggableError { let identifier: String let reason: String diff --git a/Tests/SteamPressTests/AdminTests/LoginTests.swift b/Tests/SteamPressTests/AdminTests/LoginTests.swift index 191ae0a4..47a2ba9e 100644 --- a/Tests/SteamPressTests/AdminTests/LoginTests.swift +++ b/Tests/SteamPressTests/AdminTests/LoginTests.swift @@ -2,7 +2,6 @@ import XCTest import Vapor import SteamPress import Foundation -import Authentication class LoginTests: XCTestCase { diff --git a/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift b/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift index f774f73a..40d97a14 100644 --- a/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift +++ b/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift @@ -1,5 +1,4 @@ import Vapor -import Authentication import SteamPress struct ReversedPasswordHasher: PasswordHasher, PasswordVerifier { diff --git a/Tests/SteamPressTests/Helpers/TestDataBuilder.swift b/Tests/SteamPressTests/Helpers/TestDataBuilder.swift index 03a2e40b..00ca976e 100644 --- a/Tests/SteamPressTests/Helpers/TestDataBuilder.swift +++ b/Tests/SteamPressTests/Helpers/TestDataBuilder.swift @@ -1,7 +1,6 @@ import Foundation @testable import SteamPress import Vapor -import Authentication struct TestDataBuilder { diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift index 8a42c63c..381b76cb 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift @@ -1,6 +1,5 @@ import SteamPress import Vapor -import Authentication extension TestWorld { static func getSteamPressApp(repository: InMemoryRepository, From 567c450470627247b9eca70c68e661dc8ba5221d Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 10:31:23 +0000 Subject: [PATCH 13/70] More fixes, including password stuff which needs to be PRed into Vapor --- Package.swift | 2 + .../Controllers/Admin/LoginController.swift | 65 ++++++++++--------- .../Admin/PostAdminController.swift | 4 +- .../Extensions/BCrypt+PasswordHasher.swift | 16 ++++- .../Extensions/Models+Parameters.swift | 6 +- .../Extensions/Request+PageInformation.swift | 4 +- .../SteamPressRandomNumberGenerator.swift | 32 ++++++++- Sources/SteamPress/Views/PaginatorTag.swift | 7 +- 8 files changed, 92 insertions(+), 44 deletions(-) diff --git a/Package.swift b/Package.swift index 27d13f86..47e3b607 100644 --- a/Package.swift +++ b/Package.swift @@ -13,11 +13,13 @@ let package = Package( dependencies: [ .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-rc"), .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.0.0"), + .package(url: "https://github.com/vapor/leaf-kit.git", from: "1.0.0-rc.1"), .package(name: "SwiftMarkdown", url: "https://github.com/vapor-community/markdown.git", from: "0.6.1"), ], targets: [ .target(name: "SteamPress", dependencies: [ .product(name: "Vapor", package: "vapor"), + .product(name: "LeafKit", package: "leaf-kit"), "SwiftSoup", "SwiftMarkdown", // .product(name: "Authentication", package: "vapor") diff --git a/Sources/SteamPress/Controllers/Admin/LoginController.swift b/Sources/SteamPress/Controllers/Admin/LoginController.swift index ce950f5d..98923698 100644 --- a/Sources/SteamPress/Controllers/Admin/LoginController.swift +++ b/Sources/SteamPress/Controllers/Admin/LoginController.swift @@ -1,122 +1,127 @@ import Vapor struct LoginController: RouteCollection { - + // MARK: - Properties private let pathCreator: BlogPathCreator - + // MARK: - Initialiser init(pathCreator: BlogPathCreator) { self.pathCreator = pathCreator } - + // MARK: - Route setup func boot(routes: RoutesBuilder) throws { routes.get("login", use: loginHandler) routes.post("login", use: loginPostHandler) - + let redirectMiddleware = BlogLoginRedirectAuthMiddleware(pathCreator: pathCreator) let protectedRoutes = routes.grouped(redirectMiddleware) protectedRoutes.post("logout", use: logoutHandler) protectedRoutes.get("resetPassword", use: resetPasswordHandler) protectedRoutes.post("resetPassword", use: resetPasswordPostHandler) } - + // MARK: - Route handlers func loginHandler(_ req: Request) throws -> EventLoopFuture { let loginRequied = (try? req.query.get(Bool.self, at: "loginRequired")) != nil return try req.blogPresenter.loginView(loginWarning: loginRequied, errors: nil, username: nil, usernameError: false, passwordError: false, rememberMe: false, pageInformation: req.pageInformation()) } - + func loginPostHandler(_ req: Request) throws -> EventLoopFuture { let loginData = try req.content.decode(LoginData.self) var loginErrors = [String]() var usernameError = false var passwordError = false - + if loginData.username == nil { loginErrors.append("You must supply your username") usernameError = true } - + if loginData.password == nil { loginErrors.append("You must supply your password") passwordError = true } - + if !loginErrors.isEmpty { return try req.blogPresenter.loginView(loginWarning: false, errors: loginErrors, username: loginData.username, usernameError: usernameError, passwordError: passwordError, rememberMe: loginData.rememberMe ?? false, pageInformation: req.pageInformation()).encodeResponse(for: req) } - + guard let username = loginData.username, let password = loginData.password else { throw Abort(.internalServerError) } - + if let rememberMe = loginData.rememberMe, rememberMe { try req.session()["SteamPressRememberMe"] = "YES" } else { try req.session()["SteamPressRememberMe"] = nil } - + return req.blogUserRepository.getUser(username: username).flatMap { user -> EventLoopFuture in - guard let user = user, try req.passwordVerifier.verify(password, created: user.password) else { - let loginError = ["Your username or password is incorrect"] - return try req.blogPresenter.loginView(loginWarning: false, errors: loginError, username: loginData.username, usernameError: false, passwordError: false, rememberMe: loginData.rememberMe ?? false, pageInformation: req.pageInformation()).encodeResponse(for: req) + do { + guard let user = user, try req.passwordVerifier.verify(password, created: user.password) else { + let loginError = ["Your username or password is incorrect"] + return try req.blogPresenter.loginView(loginWarning: false, errors: loginError, username: loginData.username, usernameError: false, passwordError: false, rememberMe: loginData.rememberMe ?? false, pageInformation: req.pageInformation()).encodeResponse(for: req) + } + try user.authenticateSession(on: req) + return req.eventLoop.future(req.redirect(to: self.pathCreator.createPath(for: "admin"))) + } + catch { + return req.eventLoop.makeFailedFuture(error) } - try user.authenticateSession(on: req) - return req.eventLoop.future(req.redirect(to: self.pathCreator.createPath(for: "admin"))) } } - + func logoutHandler(_ request: Request) throws -> Response { try request.unauthenticateBlogUserSession() return request.redirect(to: pathCreator.createPath(for: pathCreator.blogPath)) } - + func resetPasswordHandler(_ req: Request) throws -> EventLoopFuture { try req.adminPresenter.createResetPasswordView(errors: nil, passwordError: nil, confirmPasswordError: nil, pageInformation: req.adminPageInfomation()) } - + func resetPasswordPostHandler(_ req: Request) throws -> EventLoopFuture { let data = try req.content.decode(ResetPasswordData.self) - + var resetPasswordErrors = [String]() var passwordError: Bool? var confirmPasswordError: Bool? - + guard let password = data.password, let confirmPassword = data.confirmPassword else { - + if data.password == nil { resetPasswordErrors.append("You must specify a password") passwordError = true } - + if data.confirmPassword == nil { resetPasswordErrors.append("You must confirm your password") confirmPasswordError = true } - + let view = try req.adminPresenter.createResetPasswordView(errors: resetPasswordErrors, passwordError: passwordError, confirmPasswordError: confirmPasswordError, pageInformation: req.adminPageInfomation()) return view.encodeResponse(for: req) } - + if password != confirmPassword { resetPasswordErrors.append("Your passwords must match!") passwordError = true confirmPasswordError = true } - + if password.count < 10 { passwordError = true resetPasswordErrors.append("Your password must be at least 10 characters long") } - + guard resetPasswordErrors.isEmpty else { let view = try req.adminPresenter.createResetPasswordView(errors: resetPasswordErrors, passwordError: passwordError, confirmPasswordError: confirmPasswordError, pageInformation: req.adminPageInfomation()) return view.encodeResponse(for: req) } - - let user = try req.requireAuthenticated(BlogUser.self) + + let user = try req.auth.require(BlogUser.self) user.password = try req.passwordHasher.hash(password) user.resetPasswordRequired = false let redirect = req.redirect(to: pathCreator.createPath(for: "admin")) diff --git a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift index 207b9b2e..6832c71e 100644 --- a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift @@ -26,7 +26,7 @@ struct PostAdminController: RouteCollection { func createPostPostHandler(_ req: Request) throws -> EventLoopFuture { let data = try req.content.decode(CreatePostData.self) - let author = try req.requireAuthenticated(BlogUser.self) + let author = try req.auth.require(BlogUser.self) if data.draft == nil && data.publish == nil { throw Abort(.badRequest) @@ -34,7 +34,7 @@ struct PostAdminController: RouteCollection { if let createPostErrors = validatePostCreation(data) { let view = try req.adminPresenter.createPostView(errors: createPostErrors.errors, title: data.title, contents: data.contents, slugURL: nil, tags: data.tags, isEditing: false, post: nil, isDraft: nil, titleError: createPostErrors.titleError, contentsError: createPostErrors.contentsError, pageInformation: req.adminPageInfomation()) - return try view.encode(for: req) + return view.encodeResponse(for: req) } guard let title = data.title, let contents = data.contents else { diff --git a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift index fdb2b1b1..eb1f0d4d 100644 --- a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift +++ b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift @@ -11,13 +11,23 @@ extension BCryptDigest: PasswordHasher { } } +protocol SteamPressPasswordVerifier { + func verify(_ plaintext: String, created hash: String) throws -> Bool +} + +extension BCryptDigest: SteamPressPasswordVerifier { + func verify(_ plaintext: String, created hash: String) throws -> Bool { + return try self.verify(plaintext, created: hash) + } +} + public extension Request { var passwordHasher: PasswordHasher { self.application.passwordHashers.makeHasher!(self) } - var passwordVerifier: PasswordVerifier { + internal var passwordVerifier: SteamPressPasswordVerifier { self.application.passwordVerifiers.makeVerifier!(self) } } @@ -63,8 +73,8 @@ private struct PasswordHasherFactory { } private struct PasswordVerifierFactory { - var makeVerifier: ((Request) -> PasswordVerifier)? - mutating func use(_ makeVerifier: @escaping (Request) -> PasswordVerifier) { + var makeVerifier: ((Request) -> SteamPressPasswordVerifier)? + mutating func use(_ makeVerifier: @escaping (Request) -> SteamPressPasswordVerifier) { self.makeVerifier = makeVerifier } } diff --git a/Sources/SteamPress/Extensions/Models+Parameters.swift b/Sources/SteamPress/Extensions/Models+Parameters.swift index c001d935..0050abdf 100644 --- a/Sources/SteamPress/Extensions/Models+Parameters.swift +++ b/Sources/SteamPress/Extensions/Models+Parameters.swift @@ -57,9 +57,9 @@ protocol ParameterModel { // } //} -extension Parameters { - func find(_ type: T.Type, on req: Request) -> EventLoopFuture where T: ParameterModel { - guard let idString = req.parameters.get(T.parameterKey), let id = Int(idString) else { +extension Parameters { + func findUser(on req: Request) -> EventLoopFuture { + guard let idString = req.parameters.get(BlogUser.parameterKey), let id = Int(idString) else { return req.eventLoop.makeFailedFuture(Abort(.badRequest)) } return req.blogUserRepository.getUser(id: id).unwrap(or: Abort(.notFound)) diff --git a/Sources/SteamPress/Extensions/Request+PageInformation.swift b/Sources/SteamPress/Extensions/Request+PageInformation.swift index 038397c0..2408fffa 100644 --- a/Sources/SteamPress/Extensions/Request+PageInformation.swift +++ b/Sources/SteamPress/Extensions/Request+PageInformation.swift @@ -6,10 +6,10 @@ extension Request { guard let currentEncodedURL = currentURL.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { throw SteamPressError(identifier: "STEAMPRESS", "Failed to convert page url to URL encoded") } - return try BlogGlobalPageInformation(disqusName: Environment.get("BLOG_DISQUS_NAME"), siteTwitterHandle: Environment.get("BLOG_SITE_TWITTER_HANDLE"), googleAnalyticsIdentifier: Environment.get("BLOG_GOOGLE_ANALYTICS_IDENTIFIER"), loggedInUser: authenticated(BlogUser.self), websiteURL: self.rootUrl(), currentPageURL: currentURL, currentPageEncodedURL: currentEncodedURL) + return try BlogGlobalPageInformation(disqusName: Environment.get("BLOG_DISQUS_NAME"), siteTwitterHandle: Environment.get("BLOG_SITE_TWITTER_HANDLE"), googleAnalyticsIdentifier: Environment.get("BLOG_GOOGLE_ANALYTICS_IDENTIFIER"), loggedInUser: self.auth.get(BlogUser.self), websiteURL: self.rootUrl(), currentPageURL: currentURL, currentPageEncodedURL: currentEncodedURL) } func adminPageInfomation() throws -> BlogAdminPageInformation { - return try BlogAdminPageInformation(loggedInUser: requireAuthenticated(BlogUser.self), websiteURL: self.rootUrl(), currentPageURL: self.url()) + return try BlogAdminPageInformation(loggedInUser: self.auth.require(BlogUser.self), websiteURL: self.rootUrl(), currentPageURL: self.url()) } } diff --git a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift index d1875e80..cdb756b7 100644 --- a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift +++ b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift @@ -1,5 +1,35 @@ import Vapor -public protocol SteamPressRandomNumberGenerator: Service { +public protocol SteamPressRandomNumberGenerator { func getNumber() -> Int } + +private struct RandomNumberGeneratorFactory { + var makeRNG: ((Request) -> SteamPressRandomNumberGenerator)? + mutating func use(_ makeRNG: @escaping (Request) -> SteamPressRandomNumberGenerator) { + self.makeRNG = makeRNG + } +} + +private extension Application { + var randomNumberGenerators: RandomNumberGeneratorFactory { + get { + if let existing = self.userInfo["randomNumberGenerator"] as? RandomNumberGeneratorFactory { + return existing + } else { + let new = RandomNumberGeneratorFactory() + self.userInfo["randomNumberGenerator"] = new + return new + } + } + set { + self.userInfo["randomNumberGenerator"] = newValue + } + } +} + +public extension Request { + var randomNumberGenerator: SteamPressRandomNumberGenerator { + self.application.randomNumberGenerators.makeRNG!(self) + } +} diff --git a/Sources/SteamPress/Views/PaginatorTag.swift b/Sources/SteamPress/Views/PaginatorTag.swift index a9ae6d2e..51e105bd 100644 --- a/Sources/SteamPress/Views/PaginatorTag.swift +++ b/Sources/SteamPress/Views/PaginatorTag.swift @@ -1,6 +1,7 @@ -import TemplateKit +import LeafKit +import Foundation -public final class PaginatorTag: TagRenderer { +public final class PaginatorTag: LeafTag { public enum Error: Swift.Error { case expectedPaginationInformation } @@ -13,7 +14,7 @@ public final class PaginatorTag: TagRenderer { public static let name = "paginator" - public func render(tag: TagContext) throws -> EventLoopFuture { + public func render(_ ctx: LeafContext) throws -> LeafData { try tag.requireNoBody() guard let paginationInformaton = tag.context.data.dictionary?["paginationTagInformation"] else { From 3f87589f44b262a8b0fbaf603ed768d8d3de50ba Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 11:57:35 +0000 Subject: [PATCH 14/70] Migrate factories over to storage --- .../Admin/PostAdminController.swift | 15 ++++-- .../Presenters/BlogAdminPresenter.swift | 17 +++---- .../SteamPress/Presenters/BlogPresenter.swift | 19 +++---- .../Presenters/ViewBlogAdminPresenter.swift | 28 +++++----- .../Repositories/SteamPressRepository.swift | 51 ++++++++----------- 5 files changed, 60 insertions(+), 70 deletions(-) diff --git a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift index 6832c71e..5ea3f686 100644 --- a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift @@ -42,7 +42,12 @@ struct PostAdminController: RouteCollection { } return try BlogPost.generateUniqueSlugURL(from: title, on: req).flatMap { uniqueSlug in - let newPost = try BlogPost(title: title, contents: contents, author: author, creationDate: Date(), slugUrl: uniqueSlug, published: data.publish != nil) + let newPost: BlogPost + do { + newPost = try BlogPost(title: title, contents: contents, author: author, creationDate: Date(), slugUrl: uniqueSlug, published: data.publish != nil) + } catch { + return req.eventLoop.makeFailedFuture(error) + } return req.blogPostRepository.save(newPost).flatMap { post in var existingTagsQuery = [EventLoopFuture]() @@ -60,7 +65,7 @@ struct PostAdminController: RouteCollection { } } - return tagsSaves.flatten(on: req).flatMap { tags in + return tagsSaves.flatten(on: req.eventLoop).flatMap { tags in var tagLinks = [EventLoopFuture]() for tag in tags { tagLinks.append(req.blogTagRepository.add(tag, to: post)) @@ -88,7 +93,11 @@ struct PostAdminController: RouteCollection { func editPostHandler(_ req: Request) throws -> EventLoopFuture { return try req.parameters.next(BlogPost.self).flatMap { post in return req.blogTagRepository.getTags(for: post).flatMap { tags in - return try req.adminPresenter.createPostView(on: req, errors: nil, title: post.title, contents: post.contents, slugURL: post.slugUrl, tags: tags.map { $0.name }, isEditing: true, post: post, isDraft: !post.published, titleError: false, contentsError: false, pageInformation: req.adminPageInfomation()) + do { + return try req.adminPresenter.createPostView(errors: nil, title: post.title, contents: post.contents, slugURL: post.slugUrl, tags: tags.map { $0.name }, isEditing: true, post: post, isDraft: !post.published, titleError: false, contentsError: false, pageInformation: req.adminPageInfomation()) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } } } diff --git a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift index 00bac385..1b8c241d 100644 --- a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift @@ -9,23 +9,20 @@ public protocol BlogAdminPresenter { extension Request { var adminPresenter: BlogAdminPresenter { - self.application.adminPresenter.makePresenter!(self) + self.application.adminPresenterFactory.makePresenter!(self) } } extension Application { - var adminPresenter: AdminPresenterFactory { + private struct AdminPresenterKey: StorageKey { + typealias Value = AdminPresenterFactory + } + var adminPresenterFactory: AdminPresenterFactory { get { - if let existing = self.userInfo["blogAdminPresenter"] as? AdminPresenterFactory { - return existing - } else { - let new = AdminPresenterFactory() - self.userInfo["blogAdminPresenter"] = new - return new - } + self.storage[AdminPresenterKey.self] ?? .init() } set { - self.userInfo["blogAdminPresenter"] = newValue + self.storage[AdminPresenterKey.self] = newValue } } } diff --git a/Sources/SteamPress/Presenters/BlogPresenter.swift b/Sources/SteamPress/Presenters/BlogPresenter.swift index ff4edb08..54801256 100644 --- a/Sources/SteamPress/Presenters/BlogPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogPresenter.swift @@ -13,28 +13,25 @@ public protocol BlogPresenter { extension Request { var blogPresenter: BlogPresenter { - self.application.presenter.makePresenter!(self) + self.application.blogPresenterFactory.makePresenter!(self) } } extension Application { - var presenter: PresenterFactory { + private struct BlogPresenterKey: StorageKey { + typealias Value = BlogPresenterFactory + } + var blogPresenterFactory: BlogPresenterFactory { get { - if let existing = self.userInfo["blogPresenter"] as? PresenterFactory { - return existing - } else { - let new = PresenterFactory() - self.userInfo["blogPresenter"] = new - return new - } + self.storage[BlogPresenterKey.self] ?? .init() } set { - self.userInfo["blogPresenter"] = newValue + self.storage[BlogPresenterKey.self] = newValue } } } -struct PresenterFactory { +struct BlogPresenterFactory { var makePresenter: ((Request) -> BlogPresenter)? mutating func use(_ makePresenter: @escaping (Request) -> BlogPresenter) { self.makePresenter = makePresenter diff --git a/Sources/SteamPress/Presenters/ViewBlogAdminPresenter.swift b/Sources/SteamPress/Presenters/ViewBlogAdminPresenter.swift index 7c3df1e7..300ba2e5 100644 --- a/Sources/SteamPress/Presenters/ViewBlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/ViewBlogAdminPresenter.swift @@ -1,13 +1,13 @@ import Vapor public struct ViewBlogAdminPresenter: BlogAdminPresenter { - + let pathCreator: BlogPathCreator let viewRenderer: ViewRenderer let eventLoopGroup: EventLoopGroup let longDateFormatter: LongPostDateFormatter let numericDateFormatter: NumericPostDateFormatter - + public func createIndexView(posts: [BlogPost], users: [BlogUser], errors: [String]?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { do { let publishedPosts = try posts.filter { $0.published }.convertToViewBlogPostsWithoutTags(authors: users, longDateFormatter: longDateFormatter, numericDateFormatter: numericDateFormatter) @@ -18,7 +18,7 @@ public struct ViewBlogAdminPresenter: BlogAdminPresenter { return eventLoopGroup.future(error: error) } } - + public func createPostView(errors: [String]?, title: String?, contents: String?, slugURL: String?, tags: [String]?, isEditing: Bool, post: BlogPost?, isDraft: Bool?, titleError: Bool, contentsError: Bool, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { if isEditing { guard post != nil else { @@ -31,25 +31,21 @@ public struct ViewBlogAdminPresenter: BlogAdminPresenter { let context = CreatePostPageContext(title: pageTitle, editing: isEditing, post: post, draft: isDraft ?? false, errors: errors, titleSupplied: title, contentsSupplied: contents, tagsSupplied: tags, slugURLSupplied: slugURL, titleError: titleError, contentsError: contentsError, postPathPrefix: postPathPrefix.absoluteString, pageInformation: pageInformation) return viewRenderer.render("blog/admin/createPost", context) } - + public func createUserView(editing: Bool, errors: [String]?, name: String?, nameError: Bool, username: String?, usernameErorr: Bool, passwordError: Bool, confirmPasswordError: Bool, resetPasswordOnLogin: Bool, userID: Int?, profilePicture: String?, twitterHandle: String?, biography: String?, tagline: String?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { - do { - if editing { - guard userID != nil else { - return eventLoopGroup.future(error: SteamPressError(identifier: "ViewBlogAdminPresenter", "User ID is nil whilst editing")) - } + if editing { + guard userID != nil else { + return eventLoopGroup.future(error: SteamPressError(identifier: "ViewBlogAdminPresenter", "User ID is nil whilst editing")) } - - let context = CreateUserPageContext(editing: editing, errors: errors, nameSupplied: name, nameError: nameError, usernameSupplied: username, usernameError: usernameErorr, passwordError: passwordError, confirmPasswordError: confirmPasswordError, resetPasswordOnLoginSupplied: resetPasswordOnLogin, userID: userID, twitterHandleSupplied: twitterHandle, profilePictureSupplied: profilePicture, biographySupplied: biography, taglineSupplied: tagline, pageInformation: pageInformation) - return viewRenderer.render("blog/admin/createUser", context) - } catch { - return eventLoopGroup.future(error: error) } + + let context = CreateUserPageContext(editing: editing, errors: errors, nameSupplied: name, nameError: nameError, usernameSupplied: username, usernameError: usernameErorr, passwordError: passwordError, confirmPasswordError: confirmPasswordError, resetPasswordOnLoginSupplied: resetPasswordOnLogin, userID: userID, twitterHandleSupplied: twitterHandle, profilePictureSupplied: profilePicture, biographySupplied: biography, taglineSupplied: tagline, pageInformation: pageInformation) + return viewRenderer.render("blog/admin/createUser", context) } - + public func createResetPasswordView(errors: [String]?, passwordError: Bool?, confirmPasswordError: Bool?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { let context = ResetPasswordPageContext(errors: errors, passwordError: passwordError, confirmPasswordError: confirmPasswordError, pageInformation: pageInformation) return viewRenderer.render("blog/admin/resetPassword", context) } - + } diff --git a/Sources/SteamPress/Repositories/SteamPressRepository.swift b/Sources/SteamPress/Repositories/SteamPressRepository.swift index f5870e71..d2df40d5 100644 --- a/Sources/SteamPress/Repositories/SteamPressRepository.swift +++ b/Sources/SteamPress/Repositories/SteamPressRepository.swift @@ -48,61 +48,52 @@ public protocol BlogUserRepository: SteamPressRepository { public extension Request { var blogUserRepository: BlogUserRepository { - self.application.blogUserRepositories.makeRepository!(self) + self.application.blogUserRepositoryFactory.makeRepository!(self) } var blogPostRepository: BlogPostRepository { - self.application.blogPostRepositories.makeRepository!(self) + self.application.blogPostRepositoryFactory.makeRepository!(self) } var blogTagRepository: BlogTagRepository { - self.application.blogTagRepositories.makeRepository!(self) + self.application.blogTagRepositoryFactory.makeRepository!(self) } } private extension Application { - var blogUserRepositories: BlogUserRepositoryFactory { + private struct BlogUserRepositoryKey: StorageKey { + typealias Value = BlogUserRepositoryFactory + } + var blogUserRepositoryFactory: BlogUserRepositoryFactory { get { - if let existing = self.userInfo["blogUserRepository"] as? BlogUserRepositoryFactory { - return existing - } else { - let new = BlogUserRepositoryFactory() - self.userInfo["blogUserRepository"] = new - return new - } + self.storage[BlogUserRepositoryKey.self] ?? .init() } set { - self.userInfo["blogUserRepository"] = newValue + self.storage[BlogUserRepositoryKey.self] = newValue } } - var blogPostRepositories: BlogPostRepositoryFactory { + private struct BlogPostRepositoryKey: StorageKey { + typealias Value = BlogPostRepositoryFactory + } + var blogPostRepositoryFactory: BlogPostRepositoryFactory { get { - if let existing = self.userInfo["blogPostRepository"] as? BlogPostRepositoryFactory { - return existing - } else { - let new = BlogPostRepositoryFactory() - self.userInfo["blogPostRepository"] = new - return new - } + self.storage[BlogPostRepositoryKey.self] ?? .init() } set { - self.userInfo["blogPostRepository"] = newValue + self.storage[BlogPostRepositoryKey.self] = newValue } } - var blogTagRepositories: BlogTagRepositoryFactory { + private struct BlogTagRepositoryKey: StorageKey { + typealias Value = BlogTagRepositoryFactory + } + var blogTagRepositoryFactory: BlogTagRepositoryFactory { get { - if let existing = self.userInfo["blogTagRepository"] as? BlogTagRepositoryFactory { - return existing - } else { - let new = BlogTagRepositoryFactory() - self.userInfo["blogTagRepository"] = new - return new - } + self.storage[BlogTagRepositoryKey.self] ?? .init() } set { - self.userInfo["blogTagRepository"] = newValue + self.storage[BlogTagRepositoryKey.self] = newValue } } } From 84be3076f61ab0295c8f0a106c45ad11ffaf753b Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 12:11:06 +0000 Subject: [PATCH 15/70] Fix errors in PostAdminController --- .../Admin/PostAdminController.swift | 40 +++++++++++-------- .../Extensions/Models+Parameters.swift | 7 ++++ Sources/SteamPress/Models/BlogPost.swift | 3 +- Sources/SteamPress/Models/BlogUser.swift | 6 +-- 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift index 5ea3f686..4f73d4a0 100644 --- a/Sources/SteamPress/Controllers/Admin/PostAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/PostAdminController.swift @@ -74,7 +74,7 @@ struct PostAdminController: RouteCollection { tagLinks.append(req.blogTagRepository.add(tag, to: post)) } let redirect = req.redirect(to: self.pathCreator.createPath(for: "posts/\(post.slugUrl)")) - return tagLinks.flatten(on: req).transform(to: redirect) + return tagLinks.flatten(on: req.eventLoop).transform(to: redirect) } } } @@ -82,7 +82,7 @@ struct PostAdminController: RouteCollection { } func deletePostHandler(_ req: Request) throws -> EventLoopFuture { - return try req.parameters.next(BlogPost.self).flatMap { post in + return req.parameters.findPost(on: req).flatMap { post in return req.blogTagRepository.deleteTags(for: post).flatMap { let redirect = req.redirect(to: self.pathCreator.createPath(for: "admin")) return req.blogPostRepository.delete(post).transform(to: redirect) @@ -91,7 +91,7 @@ struct PostAdminController: RouteCollection { } func editPostHandler(_ req: Request) throws -> EventLoopFuture { - return try req.parameters.next(BlogPost.self).flatMap { post in + return req.parameters.findPost(on: req).flatMap { post in return req.blogTagRepository.getTags(for: post).flatMap { tags in do { return try req.adminPresenter.createPostView(errors: nil, title: post.title, contents: post.contents, slugURL: post.slugUrl, tags: tags.map { $0.name }, isEditing: true, post: post, isDraft: !post.published, titleError: false, contentsError: false, pageInformation: req.adminPageInfomation()) @@ -104,13 +104,17 @@ struct PostAdminController: RouteCollection { func editPostPostHandler(_ req: Request) throws -> EventLoopFuture { let data = try req.content.decode(CreatePostData.self) - return try req.parameters.next(BlogPost.self).flatMap { post in + return req.parameters.findPost(on: req).flatMap { post -> EventLoopFuture in if let errors = self.validatePostCreation(data) { - return try req.adminPresenter.createPostView(on: req, errors: errors.errors, title: data.title, contents: data.contents, slugURL: post.slugUrl, tags: data.tags, isEditing: true, post: post, isDraft: !post.published, titleError: errors.titleError, contentsError: errors.contentsError, pageInformation: req.adminPageInfomation()).encode(for: req) + do { + return try req.adminPresenter.createPostView(errors: errors.errors, title: data.title, contents: data.contents, slugURL: post.slugUrl, tags: data.tags, isEditing: true, post: post, isDraft: !post.published, titleError: errors.titleError, contentsError: errors.contentsError, pageInformation: req.adminPageInfomation()).encodeResponse(for: req) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } guard let title = data.title, let contents = data.contents else { - throw Abort(.internalServerError) + return req.eventLoop.makeFailedFuture(Abort(.internalServerError)) } post.title = title @@ -118,9 +122,13 @@ struct PostAdminController: RouteCollection { let slugURLFuture: EventLoopFuture if let updateSlugURL = data.updateSlugURL, updateSlugURL { - slugURLFuture = try BlogPost.generateUniqueSlugURL(from: title, on: req) + do { + slugURLFuture = try BlogPost.generateUniqueSlugURL(from: title, on: req) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } else { - slugURLFuture = req.future(post.slugUrl) + slugURLFuture = req.eventLoop.future(post.slugUrl) } return slugURLFuture.flatMap { slugURL in @@ -134,7 +142,7 @@ struct PostAdminController: RouteCollection { } } - return req.blogTagRepository.getTags(for: post).and(req.blogTagRepository.getAllTags()) { existingTags, allTags in + return req.blogTagRepository.getTags(for: post).and(req.blogTagRepository.getAllTags()).flatMap { existingTags, allTags in let tagsToUnlink = existingTags.filter { (anExistingTag) -> Bool in for tagName in data.tags { if anExistingTag.name == tagName { @@ -145,7 +153,7 @@ struct PostAdminController: RouteCollection { } var removeTagLinkResults = [EventLoopFuture]() for tagToUnlink in tagsToUnlink { - removeTagLinkResults.append(tagsRepository.remove(tagToUnlink, from: post, on: req)) + removeTagLinkResults.append(req.blogTagRepository.remove(tagToUnlink, from: post)) } let newTagsNames = data.tags.filter { (tagName) -> Bool in @@ -158,21 +166,21 @@ struct PostAdminController: RouteCollection { for newTagName in newTagsNames { let foundInAllTags = allTags.filter { $0.name == newTagName }.first if let existingTag = foundInAllTags { - tagCreateSaves.append(req.future(existingTag)) + tagCreateSaves.append(req.eventLoop.future(existingTag)) } else { let newTag = BlogTag(name: newTagName) - tagCreateSaves.append(tagsRepository.save(newTag, on: req)) + tagCreateSaves.append(req.blogTagRepository.save(newTag)) } } - return removeTagLinkResults.flatten(on: req).and(tagCreateSaves.flatten(on: req)).flatMap { (_, newTags) in + return removeTagLinkResults.flatten(on: req.eventLoop).and(tagCreateSaves.flatten(on: req.eventLoop)).flatMap { (_, newTags) in var postTagLinkResults = [EventLoopFuture]() for tag in newTags { - postTagLinkResults.append(tagsRepository.add(tag, to: post, on: req)) + postTagLinkResults.append(req.blogTagRepository.add(tag, to: post)) } - return postTagLinkResults.flatten(on: req).flatMap { + return postTagLinkResults.flatten(on: req.eventLoop).flatMap { let redirect = req.redirect(to: self.pathCreator.createPath(for: "posts/\(post.slugUrl)")) - return req.blogPostRepository.save(post, on: req).transform(to: redirect) + return req.blogPostRepository.save(post).transform(to: redirect) } } } diff --git a/Sources/SteamPress/Extensions/Models+Parameters.swift b/Sources/SteamPress/Extensions/Models+Parameters.swift index 0050abdf..2934fbf5 100644 --- a/Sources/SteamPress/Extensions/Models+Parameters.swift +++ b/Sources/SteamPress/Extensions/Models+Parameters.swift @@ -64,4 +64,11 @@ extension Parameters { } return req.blogUserRepository.getUser(id: id).unwrap(or: Abort(.notFound)) } + + func findPost(on req: Request) -> EventLoopFuture { + guard let idString = req.parameters.get(BlogPost.parameterKey), let id = Int(idString) else { + return req.eventLoop.makeFailedFuture(Abort(.badRequest)) + } + return req.blogPostRepository.getPost(id: id).unwrap(or: Abort(.notFound)) + } } diff --git a/Sources/SteamPress/Models/BlogPost.swift b/Sources/SteamPress/Models/BlogPost.swift index 303ad713..63035a4b 100644 --- a/Sources/SteamPress/Models/BlogPost.swift +++ b/Sources/SteamPress/Models/BlogPost.swift @@ -69,8 +69,7 @@ extension BlogPost { .replacingOccurrences(of: " ", with: "-", options: .regularExpression) return req.blogPostRepository.getPost(slug: initialSlug).map { postWithSameSlug in if postWithSameSlug != nil { - let randomNumberGenerator = try req.make(SteamPressRandomNumberGenerator.self) - let randomNumber = randomNumberGenerator.getNumber() + let randomNumber = req.randomNumberGenerator.getNumber() return "\(initialSlug)-\(randomNumber)" } else { return initialSlug diff --git a/Sources/SteamPress/Models/BlogUser.swift b/Sources/SteamPress/Models/BlogUser.swift index f67808bf..26fbbb3e 100644 --- a/Sources/SteamPress/Models/BlogUser.swift +++ b/Sources/SteamPress/Models/BlogUser.swift @@ -32,16 +32,16 @@ public final class BlogUser: Codable { extension BlogUser: Authenticatable { func authenticateSession(on req: Request) throws { try req.session()["_BlogUserSession"] = self.userID?.description - try req.authenticate(self) + req.auth.login(self) } } extension Request { func unauthenticateBlogUserSession() throws { - guard try self.hasSession() else { + guard self.hasSession else { return } try session()["_BlogUserSession"] = nil - try unauthenticate(BlogUser.self) + self.auth.logout(BlogUser.self) } } From 425c8f03364c849f24b1084ffc7c4d86dd15977f Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 12:24:37 +0000 Subject: [PATCH 16/70] More code compiling --- .../Admin/UserAdminController.swift | 12 ++-- .../Controllers/BlogAdminController.swift | 6 +- .../Controllers/BlogController.swift | 68 +++++++++++++------ .../Extensions/Models+Parameters.swift | 7 ++ .../Feed Generators/AtomFeedGenerator.swift | 32 +++++---- .../Feed Generators/RSSFeedGenerator.swift | 41 +++++------ .../BlogLoginRedirectAuthMiddleware.swift | 2 +- 7 files changed, 108 insertions(+), 60 deletions(-) diff --git a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift index 8029ef92..012deee9 100644 --- a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift @@ -55,13 +55,17 @@ struct UserAdminController: RouteCollection { } func editUserHandler(_ req: Request) throws -> EventLoopFuture { - return req.parameters.find(BlogUser.self, on: req).flatMap { user in - return try req.adminPresenter.createUserView(editing: true, errors: nil, name: user.name, nameError: false, username: user.username, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: user.resetPasswordRequired, userID: user.userID, profilePicture: user.profilePicture, twitterHandle: user.twitterHandle, biography: user.biography, tagline: user.tagline, pageInformation: req.adminPageInfomation()) + req.parameters.findUser(on: req).flatMap { user in + do { + return try req.adminPresenter.createUserView(editing: true, errors: nil, name: user.name, nameError: false, username: user.username, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: user.resetPasswordRequired, userID: user.userID, profilePicture: user.profilePicture, twitterHandle: user.twitterHandle, biography: user.biography, tagline: user.tagline, pageInformation: req.adminPageInfomation()) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } } func editUserPostHandler(_ req: Request) throws -> EventLoopFuture { - return req.parameters.find(BlogUser.self, on: req).flatMap { user in + req.parameters.findUser(on: req).flatMap { user in let data = try req.content.decode(CreateUserData.self) guard let name = data.name, let username = data.username else { @@ -103,7 +107,7 @@ struct UserAdminController: RouteCollection { } func deleteUserPostHandler(_ req: Request) throws -> EventLoopFuture { - req.parameters.find(BlogUser.self, on: req).and(req.blogUserRepository.getUsersCount()).flatMap { user, userCount in + req.parameters.findUser(on: req).and(req.blogUserRepository.getUsersCount()).flatMap { user, userCount in guard userCount > 1 else { return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: true).and(req.blogUserRepository.getAllUsers()).flatMap { posts, users in let view = try req.adminPresenter.createIndexView(posts: posts, users: users, errors: ["You cannot delete the last user"], pageInformation: req.adminPageInfomation()) diff --git a/Sources/SteamPress/Controllers/BlogAdminController.swift b/Sources/SteamPress/Controllers/BlogAdminController.swift index 81a64c0c..25a4fa68 100644 --- a/Sources/SteamPress/Controllers/BlogAdminController.swift +++ b/Sources/SteamPress/Controllers/BlogAdminController.swift @@ -29,7 +29,11 @@ struct BlogAdminController: RouteCollection { // MARK: Admin Handler func adminHandler(_ req: Request) throws -> EventLoopFuture { return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: true).and(req.blogUserRepository.getAllUsers()).flatMap { posts, users in - return try req.adminPresenter.createIndexView(posts: posts, users: users, errors: nil, pageInformation: req.adminPageInfomation()) + do { + return try req.adminPresenter.createIndexView(posts: posts, users: users, errors: nil, pageInformation: req.adminPageInfomation()) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } } diff --git a/Sources/SteamPress/Controllers/BlogController.swift b/Sources/SteamPress/Controllers/BlogController.swift index a83ac3af..40904538 100644 --- a/Sources/SteamPress/Controllers/BlogController.swift +++ b/Sources/SteamPress/Controllers/BlogController.swift @@ -44,7 +44,11 @@ struct BlogController: RouteCollection { return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: false, count: postsPerPage, offset: paginationInformation.offset).and(req.blogTagRepository.getAllTags()).flatMap { posts, tags in req.blogUserRepository.getAllUsers().and(req.blogPostRepository.getAllPostsCount(includeDrafts: false)).flatMap { users, totalPostCount in req.blogTagRepository.getTagsForAllPosts().flatMap { tagsForPosts in - return req.blogPresenter.indexView(posts: posts, tags: tags, authors: users, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPostCount, currentQuery: req.url.query)) + do { + return req.blogPresenter.indexView(posts: posts, tags: tags, authors: users, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPostCount, currentQuery: req.url.query)) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } } } @@ -59,8 +63,8 @@ struct BlogController: RouteCollection { throw Abort(.badRequest) } return req.blogPostRepository.getPost(slug: blogSlug).unwrap(or: Abort(.notFound)).flatMap { (post: BlogPost) -> EventLoopFuture in - let tagsQuery: EventLoopFuture<[BlogTag]> = req.blogTagsRepository.getTags(for: post, on: req) - let userQuery: EventLoopFuture = req.blogUserRepository.getUser(id: post.author, on: req).unwrap(or: Abort(.internalServerError)) + let tagsQuery: EventLoopFuture<[BlogTag]> = req.blogTagRepository.getTags(for: post) + let userQuery: EventLoopFuture = req.blogUserRepository.getUser(id: post.author).unwrap(or: Abort(.internalServerError)) return userQuery.and(tagsQuery).flatMap { (user: BlogUser, tags: [BlogTag]) -> EventLoopFuture in do { let pageInformation: BlogGlobalPageInformation = try req.pageInformation() @@ -73,14 +77,20 @@ struct BlogController: RouteCollection { } func tagViewHandler(_ req: Request) throws -> EventLoopFuture { - return try req.parameters.next(BlogTag.self).flatMap { tag in + return req.parameters.findTag(on: req).flatMap { tag in let paginationInformation = req.getPaginationInformation(postsPerPage: self.postsPerPage) let postsQuery = req.blogPostRepository.getSortedPublishedPosts(for: tag, count: self.postsPerPage, offset: paginationInformation.offset) let postCountQuery = req.blogPostRepository.getPublishedPostCount(for: tag) - let usersQuery = req.blogUsersRepository.getAllUsers() - return flatMap(postsQuery, postCountQuery, usersQuery) { posts, totalPosts, authors in - let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPosts, currentQuery: req.http.url.query) - return req.blogPresenter.tagView(tag: tag, posts: posts, authors: authors, totalPosts: totalPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + let usersQuery = req.blogUserRepository.getAllUsers() + return postsQuery.and(postCountQuery).flatMap { posts, totalPosts in + usersQuery.flatMap { authors in + let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPosts, currentQuery: req.url.query) + do { + return req.blogPresenter.tagView(tag: tag, posts: posts, authors: authors, totalPosts: totalPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + } catch { + return req.eventLoop.makeFailedFuture(error) + } + } } } } @@ -92,7 +102,7 @@ struct BlogController: RouteCollection { let paginationInformation = req.getPaginationInformation(postsPerPage: postsPerPage) return req.blogUserRepository.getUser(username: authorUsername).flatMap { user in guard let author = user else { - throw Abort(.notFound) + return req.eventLoop.makeFailedFuture(Abort(.notFound)) } let authorPostQuery = req.blogPostRepository.getAllPostsSortedByPublishDate(for: author, includeDrafts: false, count: self.postsPerPage, offset: paginationInformation.offset) let tagQuery = req.blogTagRepository.getTagsForAllPosts() @@ -100,7 +110,11 @@ struct BlogController: RouteCollection { return authorPostQuery.and(authorPostCountQuery).flatMap { posts, postCount in tagQuery.flatMap { tagsForPosts in let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: postCount, currentQuery: req.url.query) - return req.blogPresenter.authorView(author: author, posts: posts, postCount: postCount, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + do { + return req.blogPresenter.authorView(author: author, posts: posts, postCount: postCount, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } } } @@ -109,26 +123,34 @@ struct BlogController: RouteCollection { func allTagsViewHandler(_ req: Request) throws -> EventLoopFuture { return req.blogTagRepository.getAllTagsWithPostCount().flatMap { tagswithCount in let allTags = tagswithCount.map { $0.0 } - let tagCounts = try tagswithCount.reduce(into: [Int: Int]()) { - guard let tagID = $1.0.tagID else { - throw SteamPressError(identifier: "BlogController", "Tag ID not set") + do { + let tagCounts = try tagswithCount.reduce(into: [Int: Int]()) { + guard let tagID = $1.0.tagID else { + throw SteamPressError(identifier: "BlogController", "Tag ID not set") + } + return $0[tagID] = $1.1 } - return $0[tagID] = $1.1 + return req.blogPresenter.allTagsView(tags: allTags, tagPostCounts: tagCounts, pageInformation: try req.pageInformation()) + } catch { + return req.eventLoop.makeFailedFuture(error) } - return req.blogPresenter.allTagsView(tags: allTags, tagPostCounts: tagCounts, pageInformation: try req.pageInformation()) } } func allAuthorsViewHandler(_ req: Request) throws -> EventLoopFuture { return req.blogUserRepository.getAllUsersWithPostCount().flatMap { allUsersWithCount in let allUsers = allUsersWithCount.map { $0.0 } - let authorCounts = try allUsersWithCount.reduce(into: [Int: Int]()) { - guard let userID = $1.0.userID else { - throw SteamPressError(identifier: "BlogController", "User ID not set") + do { + let authorCounts = try allUsersWithCount.reduce(into: [Int: Int]()) { + guard let userID = $1.0.userID else { + throw SteamPressError(identifier: "BlogController", "User ID not set") + } + return $0[userID] = $1.1 } - return $0[userID] = $1.1 + return req.blogPresenter.allAuthorsView(authors: allUsers, authorPostCounts: authorCounts, pageInformation: try req.pageInformation()) + } catch { + return req.eventLoop.makeFailedFuture(error) } - return req.blogPresenter.allAuthorsView(authors: allUsers, authorPostCounts: authorCounts, pageInformation: try req.pageInformation()) } } @@ -146,7 +168,11 @@ struct BlogController: RouteCollection { return postsQuery.and(postsCountQuery).flatMap { posts, totalPosts in userQuery.and(tagsQuery).flatMap { users, tagsForPosts in let paginationTagInfo = self.getPaginationInformation(currentPage: paginationInformation.page, totalPosts: totalPosts, currentQuery: req.url.query) - return req.blogPresenter.searchView(totalResults: totalPosts, posts: posts, authors: users, searchTerm: searchTerm, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + do { + return req.blogPresenter.searchView(totalResults: totalPosts, posts: posts, authors: users, searchTerm: searchTerm, tagsForPosts: tagsForPosts, pageInformation: try req.pageInformation(), paginationTagInfo: paginationTagInfo) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } } } diff --git a/Sources/SteamPress/Extensions/Models+Parameters.swift b/Sources/SteamPress/Extensions/Models+Parameters.swift index 2934fbf5..72b3cea5 100644 --- a/Sources/SteamPress/Extensions/Models+Parameters.swift +++ b/Sources/SteamPress/Extensions/Models+Parameters.swift @@ -71,4 +71,11 @@ extension Parameters { } return req.blogPostRepository.getPost(id: id).unwrap(or: Abort(.notFound)) } + + func findTag(on req: Request) -> EventLoopFuture { + guard let tagName = req.parameters.get(BlogTag.parameterKey) else { + return req.eventLoop.makeFailedFuture(Abort(.notFound)) + } + return req.blogTagRepository.getTag(tagName).unwrap(or: Abort(.notFound)) + } } diff --git a/Sources/SteamPress/Feed Generators/AtomFeedGenerator.swift b/Sources/SteamPress/Feed Generators/AtomFeedGenerator.swift index 581f4527..561d52d2 100644 --- a/Sources/SteamPress/Feed Generators/AtomFeedGenerator.swift +++ b/Sources/SteamPress/Feed Generators/AtomFeedGenerator.swift @@ -87,23 +87,27 @@ fileprivate extension BlogPost { func getPostAtomFeed(blogPath: String, dateFormatter: DateFormatter, for request: Request) throws -> EventLoopFuture { let updatedTime = lastEdited ?? created return request.blogUserRepository.getUser(id: author).flatMap { user in - guard let user = user else { - throw SteamPressError(identifier: "Invalid-relationship", "Blog user with ID \(self.author) not found") - } - guard let postID = self.blogID else { - throw SteamPressError(identifier: "ID-required", "Blog Post has no ID") - } - var postEntry = "\n\(blogPath)/posts-id/\(postID)/\n\(self.title)\n\(dateFormatter.string(from: updatedTime))\n\(dateFormatter.string(from: self.created))\n\n\(user.name)\n\(blogPath)/authors/\(user.username)/\n\n\(try self.description())\n\n" + do { + guard let user = user else { + throw SteamPressError(identifier: "Invalid-relationship", "Blog user with ID \(self.author) not found") + } + guard let postID = self.blogID else { + throw SteamPressError(identifier: "ID-required", "Blog Post has no ID") + } + var postEntry = "\n\(blogPath)/posts-id/\(postID)/\n\(self.title)\n\(dateFormatter.string(from: updatedTime))\n\(dateFormatter.string(from: self.created))\n\n\(user.name)\n\(blogPath)/authors/\(user.username)/\n\n\(try self.description())\n\n" - return request.blogTagRepository.getTags(for: self).map { tags in - for tag in tags { - if let percentDecodedTag = tag.name.removingPercentEncoding { - postEntry += "\n" + return request.blogTagRepository.getTags(for: self).map { tags in + for tag in tags { + if let percentDecodedTag = tag.name.removingPercentEncoding { + postEntry += "\n" + } } - } - postEntry += "\n" - return postEntry + postEntry += "\n" + return postEntry + } + } catch { + return request.eventLoop.makeFailedFuture(error) } } } diff --git a/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift b/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift index d7cf3303..5bcbd28b 100644 --- a/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift +++ b/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift @@ -29,31 +29,34 @@ struct RSSFeedGenerator { // MARK: - Route Handler func feedHandler(_ request: Request) throws -> EventLoopFuture { - request.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: false).flatMap { posts in - var xmlFeed = try self.getXMLStart(for: request) + do { + var xmlFeed = try self.getXMLStart(for: request) - if !posts.isEmpty { - let postDate = posts[0].lastEdited ?? posts[0].created - xmlFeed += "\(self.rfc822DateFormatter.string(from: postDate))\n" - } + if !posts.isEmpty { + let postDate = posts[0].lastEdited ?? posts[0].created + xmlFeed += "\(self.rfc822DateFormatter.string(from: postDate))\n" + } - xmlFeed += try "\nSearch \(self.title)\nSearch\n\(self.getRootPath(for: request))/search?\nterm\n\n" + xmlFeed += try "\nSearch \(self.title)\nSearch\n\(self.getRootPath(for: request))/search?\nterm\n\n" - var postData: [EventLoopFuture] = [] - for post in posts { - try postData.append(post.getPostRSSFeed(rootPath: self.getRootPath(for: request), dateFormatter: self.rfc822DateFormatter, for: request)) - } - - return postData.flatten(on: request.eventLoop).map { postInformation in - for post in postInformation { - xmlFeed += post + var postData: [EventLoopFuture] = [] + for post in posts { + try postData.append(post.getPostRSSFeed(rootPath: self.getRootPath(for: request), dateFormatter: self.rfc822DateFormatter, for: request)) } - xmlFeed += self.xmlEnd - var httpResponse = Response(body: .init(stringLiteral: xmlFeed)) - httpResponse.headers.add(name: .contentType, value: "application/rss+xml") - return httpResponse + return postData.flatten(on: request.eventLoop).map { postInformation in + for post in postInformation { + xmlFeed += post + } + + xmlFeed += self.xmlEnd + var httpResponse = Response(body: .init(stringLiteral: xmlFeed)) + httpResponse.headers.add(name: .contentType, value: "application/rss+xml") + return httpResponse + } + } catch { + return request.eventLoop.makeFailedFuture(error) } } } diff --git a/Sources/SteamPress/Middleware/BlogLoginRedirectAuthMiddleware.swift b/Sources/SteamPress/Middleware/BlogLoginRedirectAuthMiddleware.swift index 21b7082e..b5c1ab8e 100644 --- a/Sources/SteamPress/Middleware/BlogLoginRedirectAuthMiddleware.swift +++ b/Sources/SteamPress/Middleware/BlogLoginRedirectAuthMiddleware.swift @@ -6,7 +6,7 @@ struct BlogLoginRedirectAuthMiddleware: Middleware { func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { do { - let user = try request.requireAuthenticated(BlogUser.self) + let user = try request.auth.require(BlogUser.self) let resetPasswordPath = pathCreator.createPath(for: "admin/resetPassword") var requestPath = request.url.string if !requestPath.hasSuffix("/") { From a7eedd28b4f541b0c99772e324e0b8b71c1fec9b Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 12:42:06 +0000 Subject: [PATCH 17/70] Migrate password hasher stuff to storage --- .../Extensions/BCrypt+PasswordHasher.swift | 35 +++++------- .../Feed Generators/AtomFeedGenerator.swift | 54 ++++++++++--------- .../BlogAuthSessionsMiddleware.swift | 10 ++-- Sources/SteamPress/Models/BlogUser.swift | 2 + 4 files changed, 50 insertions(+), 51 deletions(-) diff --git a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift index eb1f0d4d..0fd5bd5b 100644 --- a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift +++ b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift @@ -24,45 +24,38 @@ extension BCryptDigest: SteamPressPasswordVerifier { public extension Request { var passwordHasher: PasswordHasher { - self.application.passwordHashers.makeHasher!(self) + self.application.passwordHasherFactory.makeHasher!(self) } internal var passwordVerifier: SteamPressPasswordVerifier { - self.application.passwordVerifiers.makeVerifier!(self) + self.application.passwordVerifierFactory.makeVerifier!(self) } } private extension Application { - var passwordHashers: PasswordHasherFactory { + private struct PasswordHasherKey: StorageKey { + typealias Value = PasswordHasherFactory + } + var passwordHasherFactory: PasswordHasherFactory { get { - if let existing = self.userInfo["passwordHasher"] as? PasswordHasherFactory { - return existing - } else { - let new = PasswordHasherFactory() - self.userInfo["passwordHasher"] = new - return new - } + self.storage[PasswordHasherKey.self] ?? .init() } set { - self.userInfo["passwordHasher"] = newValue + self.storage[PasswordHasherKey.self] = newValue } } - var passwordVerifiers: PasswordVerifierFactory { + private struct PasswordVerifierKey: StorageKey { + typealias Value = PasswordVerifierFactory + } + var passwordVerifierFactory: PasswordVerifierFactory { get { - if let existing = self.userInfo["passwordVerifier"] as? PasswordVerifierFactory { - return existing - } else { - let new = PasswordVerifierFactory() - self.userInfo["passwordVerifier"] = new - return new - } + self.storage[PasswordVerifierKey.self] ?? .init() } set { - self.userInfo["passwordVerifier"] = newValue + self.storage[PasswordVerifierKey.self] = newValue } } - } private struct PasswordHasherFactory { diff --git a/Sources/SteamPress/Feed Generators/AtomFeedGenerator.swift b/Sources/SteamPress/Feed Generators/AtomFeedGenerator.swift index 561d52d2..f92c68a4 100644 --- a/Sources/SteamPress/Feed Generators/AtomFeedGenerator.swift +++ b/Sources/SteamPress/Feed Generators/AtomFeedGenerator.swift @@ -31,37 +31,41 @@ struct AtomFeedGenerator { func feedHandler(_ request: Request) throws -> EventLoopFuture { return request.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: false).flatMap { posts in - var feed = try self.getFeedStart(for: request) - - if !posts.isEmpty { - let postDate = posts[0].lastEdited ?? posts[0].created - feed += "\(self.iso8601Formatter.string(from: postDate))\n" - } else { - feed += "\(self.iso8601Formatter.string(from: Date()))\n" - } + do { + var feed = try self.getFeedStart(for: request) - if let copyright = self.copyright { - feed += "\(copyright)\n" - } + if !posts.isEmpty { + let postDate = posts[0].lastEdited ?? posts[0].created + feed += "\(self.iso8601Formatter.string(from: postDate))\n" + } else { + feed += "\(self.iso8601Formatter.string(from: Date()))\n" + } - if let imageURL = self.imageURL { - feed += "\(imageURL)\n" - } + if let copyright = self.copyright { + feed += "\(copyright)\n" + } - var postData: [EventLoopFuture] = [] - for post in posts { - try postData.append(post.getPostAtomFeed(blogPath: self.getRootPath(for: request), dateFormatter: self.iso8601Formatter, for: request)) - } + if let imageURL = self.imageURL { + feed += "\(imageURL)\n" + } - return postData.flatten(on: request.eventLoop).map { postsInformation in - for postInformation in postsInformation { - feed += postInformation + var postData: [EventLoopFuture] = [] + for post in posts { + try postData.append(post.getPostAtomFeed(blogPath: self.getRootPath(for: request), dateFormatter: self.iso8601Formatter, for: request)) } - feed += self.feedEnd - var httpResponse = Response(body: .init(stringLiteral: feed)) - httpResponse.headers.add(name: .contentType, value: "application/atom+xml") - return httpResponse + return postData.flatten(on: request.eventLoop).map { postsInformation in + for postInformation in postsInformation { + feed += postInformation + } + + feed += self.feedEnd + let httpResponse = Response(body: .init(stringLiteral: feed)) + httpResponse.headers.add(name: .contentType, value: "application/atom+xml") + return httpResponse + } + } catch { + return request.eventLoop.makeFailedFuture(error) } } } diff --git a/Sources/SteamPress/Middleware/BlogAuthSessionsMiddleware.swift b/Sources/SteamPress/Middleware/BlogAuthSessionsMiddleware.swift index f8314bbf..ff7defa7 100644 --- a/Sources/SteamPress/Middleware/BlogAuthSessionsMiddleware.swift +++ b/Sources/SteamPress/Middleware/BlogAuthSessionsMiddleware.swift @@ -9,17 +9,17 @@ public final class BlogAuthSessionsMiddleware: Middleware { if let userIDString = try request.session()["_BlogUserSession"], let userID = Int(userIDString) { future = request.blogUserRepository.getUser(id: userID).flatMap { user in if let user = user { - try request.authenticate(user) + request.auth.login(user) } - return .done(on: request) + return request.eventLoop.future() } } else { - future = .done(on: request.eventLoop) + future = request.eventLoop.future() } return future.flatMap { - return next.respond(to: request).map { response in - if let user = try request.authenticated(BlogUser.self) { + return next.respond(to: request).flatMapThrowing { response in + if let user = request.auth.get(BlogUser.self) { try user.authenticateSession(on: request) } else { try request.unauthenticateBlogUserSession() diff --git a/Sources/SteamPress/Models/BlogUser.swift b/Sources/SteamPress/Models/BlogUser.swift index 26fbbb3e..98e89a5c 100644 --- a/Sources/SteamPress/Models/BlogUser.swift +++ b/Sources/SteamPress/Models/BlogUser.swift @@ -30,6 +30,7 @@ public final class BlogUser: Codable { // MARK: - Authentication extension BlogUser: Authenticatable { + #warning("this might not need to throw") func authenticateSession(on req: Request) throws { try req.session()["_BlogUserSession"] = self.userID?.description req.auth.login(self) @@ -37,6 +38,7 @@ extension BlogUser: Authenticatable { } extension Request { + #warning("this might not need to throw") func unauthenticateBlogUserSession() throws { guard self.hasSession else { return From af0c38634ad2e89ae424c312c05d985c89036d82 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 12:49:00 +0000 Subject: [PATCH 18/70] Migrate over PaginatorTag --- .../Admin/UserAdminController.swift | 23 +++++++++++++++---- Sources/SteamPress/Views/PaginatorTag.swift | 15 ++++++------ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift index 012deee9..0bc80621 100644 --- a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift @@ -110,16 +110,29 @@ struct UserAdminController: RouteCollection { req.parameters.findUser(on: req).and(req.blogUserRepository.getUsersCount()).flatMap { user, userCount in guard userCount > 1 else { return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: true).and(req.blogUserRepository.getAllUsers()).flatMap { posts, users in - let view = try req.adminPresenter.createIndexView(posts: posts, users: users, errors: ["You cannot delete the last user"], pageInformation: req.adminPageInfomation()) - return view.encodeResponse(for: req) + do { + let view = try req.adminPresenter.createIndexView(posts: posts, users: users, errors: ["You cannot delete the last user"], pageInformation: req.adminPageInfomation()) + return view.encodeResponse(for: req) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } } - let loggedInUser = try req.requireAuthenticated(BlogUser.self) + let loggedInUser: BlogUser + do { + loggedInUser = try req.auth.require(BlogUser.self) + } catch { + return req.eventLoop.makeFailedFuture(error) + } guard loggedInUser.userID != user.userID else { return req.blogPostRepository.getAllPostsSortedByPublishDate(includeDrafts: true).and(req.blogUserRepository.getAllUsers()).flatMap { posts, users in - let view = try req.adminPresenter.createIndexView(posts: posts, users: users, errors: ["You cannot delete yourself whilst logged in"], pageInformation: req.adminPageInfomation()) - return view.encodeResponse(for: req) + do { + let view = try req.adminPresenter.createIndexView(posts: posts, users: users, errors: ["You cannot delete yourself whilst logged in"], pageInformation: req.adminPageInfomation()) + return view.encodeResponse(for: req) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } } diff --git a/Sources/SteamPress/Views/PaginatorTag.swift b/Sources/SteamPress/Views/PaginatorTag.swift index 51e105bd..fdc575f4 100644 --- a/Sources/SteamPress/Views/PaginatorTag.swift +++ b/Sources/SteamPress/Views/PaginatorTag.swift @@ -15,18 +15,17 @@ public final class PaginatorTag: LeafTag { public static let name = "paginator" public func render(_ ctx: LeafContext) throws -> LeafData { - try tag.requireNoBody() + try ctx.requireNoBody() - guard let paginationInformaton = tag.context.data.dictionary?["paginationTagInformation"] else { + guard let paginationInformation = ctx.data["paginationTagInformation"]?.dictionary else { throw Error.expectedPaginationInformation } - guard let currentPage = paginationInformaton.dictionary?["currentPage"]?.int, - let totalPages = paginationInformaton.dictionary?["totalPages"]?.int else { + guard let currentPage = paginationInformation["currentPage"]?.int, let totalPages = paginationInformation["totalPages"]?.int else { throw Error.expectedPaginationInformation } - let currentQuery = paginationInformaton.dictionary?["currentQuery"]?.string + let currentQuery = paginationInformation["currentQuery"]?.string let previousPage: String? let nextPage: String? @@ -46,7 +45,7 @@ public final class PaginatorTag: LeafTag { } let data = buildNavigation(currentPage: currentPage, totalPages: totalPages, previousPage: previousPage, nextPage: nextPage, currentQuery: currentQuery) - return tag.eventLoop.future(data) + return data } } @@ -103,7 +102,7 @@ extension PaginatorTag { return links } - func buildNavigation(currentPage: Int, totalPages: Int, previousPage: String?, nextPage: String?, currentQuery: String?) -> TemplateData { + func buildNavigation(currentPage: Int, totalPages: Int, previousPage: String?, nextPage: String?, currentQuery: String?) -> LeafData { var result = "" @@ -127,7 +126,7 @@ extension PaginatorTag { result += footer - return TemplateData.string(result) + return LeafData.string(result) } func buildLink(title: String, active: Bool, link: String?, disabled: Bool) -> String { From d311de6235878244d8b163005c961f4f56fdefac Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 12:57:22 +0000 Subject: [PATCH 19/70] Keep on trucking --- .../Admin/UserAdminController.swift | 95 +++++++++++-------- .../Models/FormData/CreateUserData.swift | 11 +-- 2 files changed, 60 insertions(+), 46 deletions(-) diff --git a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift index 0bc80621..4b6e9137 100644 --- a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift @@ -29,16 +29,24 @@ struct UserAdminController: RouteCollection { return try validateUserCreation(data, on: req).flatMap { createUserErrors in if let errors = createUserErrors { - let view = try req.adminPresenter.createUserView(editing: false, errors: errors.errors, name: data.name, nameError: errors.nameError, username: data.username, usernameErorr: errors.usernameError, passwordError: errors.passwordError, confirmPasswordError: errors.confirmPasswordError, resetPasswordOnLogin: data.resetPasswordOnLogin ?? false, userID: nil, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline, pageInformation: req.adminPageInfomation()) - return view.encodeResponse(for: req) + do { + let view = try req.adminPresenter.createUserView(editing: false, errors: errors.errors, name: data.name, nameError: errors.nameError, username: data.username, usernameErorr: errors.usernameError, passwordError: errors.passwordError, confirmPasswordError: errors.confirmPasswordError, resetPasswordOnLogin: data.resetPasswordOnLogin ?? false, userID: nil, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline, pageInformation: req.adminPageInfomation()) + return view.encodeResponse(for: req) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } guard let name = data.name, let username = data.username, let password = data.password else { - throw Abort(.internalServerError) + return req.eventLoop.makeFailedFuture(Abort(.internalServerError)) } - let hasher = try req.make(PasswordHasher.self) - let hashedPassword = try hasher.hash(password) + let hashedPassword: String + do { + hashedPassword = try req.passwordHasher.hash(password) + } catch { + return req.eventLoop.makeFailedFuture(error) + } let profilePicture = data.profilePicture.isEmptyOrWhitespace() ? nil : data.profilePicture let twitterHandle = data.twitterHandle.isEmptyOrWhitespace() ? nil : data.twitterHandle let biography = data.biography.isEmptyOrWhitespace() ? nil : data.biography @@ -47,7 +55,7 @@ struct UserAdminController: RouteCollection { if let resetPasswordRequired = data.resetPasswordOnLogin, resetPasswordRequired { newUser.resetPasswordRequired = true } - return req.blogUserRepository.save(newUser, on: req).map { _ in + return req.blogUserRepository.save(newUser).map { _ in return req.redirect(to: self.pathCreator.createPath(for: "admin")) } @@ -66,42 +74,53 @@ struct UserAdminController: RouteCollection { func editUserPostHandler(_ req: Request) throws -> EventLoopFuture { req.parameters.findUser(on: req).flatMap { user in - let data = try req.content.decode(CreateUserData.self) - - guard let name = data.name, let username = data.username else { - throw Abort(.internalServerError) - } + do { + let data = try req.content.decode(CreateUserData.self) - return try self.validateUserCreation(data, editing: true, existingUsername: user.username, on: req).flatMap { errors in - if let editUserErrors = errors { - let view = try req.adminPresenter.createUserView(editing: true, errors: editUserErrors.errors, name: data.name, nameError: errors?.nameError ?? false, username: data.username, usernameErorr: errors?.usernameError ?? false, passwordError: editUserErrors.passwordError, confirmPasswordError: editUserErrors.confirmPasswordError, resetPasswordOnLogin: data.resetPasswordOnLogin ?? false, userID: user.userID, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline, pageInformation: req.adminPageInfomation()) - return view.encodeResponse(for: req) + guard let name = data.name, let username = data.username else { + throw Abort(.internalServerError) } - user.name = name - user.username = username.lowercased() - - let profilePicture = data.profilePicture.isEmptyOrWhitespace() ? nil : data.profilePicture - let twitterHandle = data.twitterHandle.isEmptyOrWhitespace() ? nil : data.twitterHandle - let biography = data.biography.isEmptyOrWhitespace() ? nil : data.biography - let tagline = data.tagline.isEmptyOrWhitespace() ? nil : data.tagline - - user.profilePicture = profilePicture - user.twitterHandle = twitterHandle - user.biography = biography - user.tagline = tagline - - if let resetPasswordOnLogin = data.resetPasswordOnLogin, resetPasswordOnLogin { - user.resetPasswordRequired = true - } + return try self.validateUserCreation(data, editing: true, existingUsername: user.username, on: req).flatMap { errors in + if let editUserErrors = errors { + do { + let view = try req.adminPresenter.createUserView(editing: true, errors: editUserErrors.errors, name: data.name, nameError: errors?.nameError ?? false, username: data.username, usernameErorr: errors?.usernameError ?? false, passwordError: editUserErrors.passwordError, confirmPasswordError: editUserErrors.confirmPasswordError, resetPasswordOnLogin: data.resetPasswordOnLogin ?? false, userID: user.userID, profilePicture: data.profilePicture, twitterHandle: data.twitterHandle, biography: data.biography, tagline: data.tagline, pageInformation: req.adminPageInfomation()) + return view.encodeResponse(for: req) + } catch { + return req.eventLoop.makeFailedFuture(error) + } + } - if let password = data.password, password != "" { - let hasher = try req.make(PasswordHasher.self) - user.password = try hasher.hash(password) - } + user.name = name + user.username = username.lowercased() + + let profilePicture = data.profilePicture.isEmptyOrWhitespace() ? nil : data.profilePicture + let twitterHandle = data.twitterHandle.isEmptyOrWhitespace() ? nil : data.twitterHandle + let biography = data.biography.isEmptyOrWhitespace() ? nil : data.biography + let tagline = data.tagline.isEmptyOrWhitespace() ? nil : data.tagline + + user.profilePicture = profilePicture + user.twitterHandle = twitterHandle + user.biography = biography + user.tagline = tagline + + if let resetPasswordOnLogin = data.resetPasswordOnLogin, resetPasswordOnLogin { + user.resetPasswordRequired = true + } - let redirect = req.redirect(to: self.pathCreator.createPath(for: "admin")) - return req.blogUserRepository.save(user).transform(to: redirect) + if let password = data.password, password != "" { + do { + user.password = try req.passwordHasher.hash(password) + } catch { + return req.eventLoop.makeFailedFuture(error) + } + } + + let redirect = req.redirect(to: self.pathCreator.createPath(for: "admin")) + return req.blogUserRepository.save(user).transform(to: redirect) + } + } catch { + return req.eventLoop.makeFailedFuture(error) } } } @@ -185,7 +204,7 @@ struct UserAdminController: RouteCollection { } do { - try data.validate() + try CreateUserData.validate(req) } catch { createUserErrors.append("The username provided is not valid") usernameError = true diff --git a/Sources/SteamPress/Models/FormData/CreateUserData.swift b/Sources/SteamPress/Models/FormData/CreateUserData.swift index 94679e41..86a6084c 100644 --- a/Sources/SteamPress/Models/FormData/CreateUserData.swift +++ b/Sources/SteamPress/Models/FormData/CreateUserData.swift @@ -13,15 +13,10 @@ struct CreateUserData: Content { } extension CreateUserData: Validatable { -// static func validations() throws -> Validations { -// var validations = Validations(CreateUserData.self) -// let usernameCharacterSet = CharacterSet(charactersIn: "-_") -// let usernameValidationCharacters = Validator.characterSet(.alphanumerics + usernameCharacterSet) -// try validations.add(\.username, usernameValidationCharacters || .nil) -// return validations -// } static func validations(_ validations: inout Validations) { - #warning("TODO") + let usernameCharacterSet = CharacterSet(charactersIn: "-_") + let usernameValidationCharacters = Validator.characterSet(.alphanumerics + usernameCharacterSet) + validations.add("username", as: String.self, is: usernameValidationCharacters) } } From f8c3ad05a25d3c86da0504f479327ac4bf7d424f Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 12:58:15 +0000 Subject: [PATCH 20/70] Migrate the random number generator over to storage --- .../Services/SteamPressRandomNumberGenerator.swift | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift index cdb756b7..db9e6a61 100644 --- a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift +++ b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift @@ -12,18 +12,15 @@ private struct RandomNumberGeneratorFactory { } private extension Application { + private struct RandomNumberGeneratorKey: StorageKey { + typealias Value = RandomNumberGeneratorFactory + } var randomNumberGenerators: RandomNumberGeneratorFactory { get { - if let existing = self.userInfo["randomNumberGenerator"] as? RandomNumberGeneratorFactory { - return existing - } else { - let new = RandomNumberGeneratorFactory() - self.userInfo["randomNumberGenerator"] = new - return new - } + self.storage[RandomNumberGeneratorKey.self] ?? .init() } set { - self.userInfo["randomNumberGenerator"] = newValue + self.storage[RandomNumberGeneratorKey.self] = newValue } } } From 4f8b40dd0708742871f765b88721f63d16d37c0f Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 13:01:45 +0000 Subject: [PATCH 21/70] Compiling without the provider --- .../SteamPress/Controllers/Admin/LoginController.swift | 10 +++++----- .../SteamPress/Feed Generators/RSSFeedGenerator.swift | 2 +- .../Middleware/BlogAuthSessionsMiddleware.swift | 8 ++++---- .../Middleware/BlogRememberMeMiddleware.swift | 6 +++--- Sources/SteamPress/Models/BlogUser.swift | 10 ++++------ 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/Sources/SteamPress/Controllers/Admin/LoginController.swift b/Sources/SteamPress/Controllers/Admin/LoginController.swift index 98923698..bcbd2751 100644 --- a/Sources/SteamPress/Controllers/Admin/LoginController.swift +++ b/Sources/SteamPress/Controllers/Admin/LoginController.swift @@ -53,9 +53,9 @@ struct LoginController: RouteCollection { } if let rememberMe = loginData.rememberMe, rememberMe { - try req.session()["SteamPressRememberMe"] = "YES" + req.session.data["SteamPressRememberMe"] = "YES" } else { - try req.session()["SteamPressRememberMe"] = nil + req.session.data["SteamPressRememberMe"] = nil } return req.blogUserRepository.getUser(username: username).flatMap { user -> EventLoopFuture in @@ -64,7 +64,7 @@ struct LoginController: RouteCollection { let loginError = ["Your username or password is incorrect"] return try req.blogPresenter.loginView(loginWarning: false, errors: loginError, username: loginData.username, usernameError: false, passwordError: false, rememberMe: loginData.rememberMe ?? false, pageInformation: req.pageInformation()).encodeResponse(for: req) } - try user.authenticateSession(on: req) + user.authenticateSession(on: req) return req.eventLoop.future(req.redirect(to: self.pathCreator.createPath(for: "admin"))) } catch { @@ -73,8 +73,8 @@ struct LoginController: RouteCollection { } } - func logoutHandler(_ request: Request) throws -> Response { - try request.unauthenticateBlogUserSession() + func logoutHandler(_ request: Request) -> Response { + request.unauthenticateBlogUserSession() return request.redirect(to: pathCreator.createPath(for: pathCreator.blogPath)) } diff --git a/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift b/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift index 5bcbd28b..16fdc7b2 100644 --- a/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift +++ b/Sources/SteamPress/Feed Generators/RSSFeedGenerator.swift @@ -51,7 +51,7 @@ struct RSSFeedGenerator { } xmlFeed += self.xmlEnd - var httpResponse = Response(body: .init(stringLiteral: xmlFeed)) + let httpResponse = Response(body: .init(stringLiteral: xmlFeed)) httpResponse.headers.add(name: .contentType, value: "application/rss+xml") return httpResponse } diff --git a/Sources/SteamPress/Middleware/BlogAuthSessionsMiddleware.swift b/Sources/SteamPress/Middleware/BlogAuthSessionsMiddleware.swift index ff7defa7..16c64e4c 100644 --- a/Sources/SteamPress/Middleware/BlogAuthSessionsMiddleware.swift +++ b/Sources/SteamPress/Middleware/BlogAuthSessionsMiddleware.swift @@ -6,7 +6,7 @@ public final class BlogAuthSessionsMiddleware: Middleware { public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { let future: EventLoopFuture - if let userIDString = try request.session()["_BlogUserSession"], let userID = Int(userIDString) { + if let userIDString = request.session.data["_BlogUserSession"], let userID = Int(userIDString) { future = request.blogUserRepository.getUser(id: userID).flatMap { user in if let user = user { request.auth.login(user) @@ -18,11 +18,11 @@ public final class BlogAuthSessionsMiddleware: Middleware { } return future.flatMap { - return next.respond(to: request).flatMapThrowing { response in + return next.respond(to: request).map { response in if let user = request.auth.get(BlogUser.self) { - try user.authenticateSession(on: request) + user.authenticateSession(on: request) } else { - try request.unauthenticateBlogUserSession() + request.unauthenticateBlogUserSession() } return response } diff --git a/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift b/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift index 0f6478de..e3715c78 100644 --- a/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift +++ b/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift @@ -3,13 +3,13 @@ import Vapor public struct BlogRememberMeMiddleware: Middleware { public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { - return next.respond(to: request).flatMapThrowing { response in - if let rememberMe = try request.session()["SteamPressRememberMe"], rememberMe == "YES" { + return next.respond(to: request).map { response in + if let rememberMe = request.session.data["SteamPressRememberMe"], rememberMe == "YES" { if var steampressCookie = response.cookies["steampress-session"] { let oneYear: TimeInterval = 60 * 60 * 24 * 365 steampressCookie.expires = Date().addingTimeInterval(oneYear) response.cookies["steampress-session"] = steampressCookie - try request.session()["SteamPressRememberMe"] = nil + request.session.data["SteamPressRememberMe"] = nil } } return response diff --git a/Sources/SteamPress/Models/BlogUser.swift b/Sources/SteamPress/Models/BlogUser.swift index 98e89a5c..69c431f1 100644 --- a/Sources/SteamPress/Models/BlogUser.swift +++ b/Sources/SteamPress/Models/BlogUser.swift @@ -30,20 +30,18 @@ public final class BlogUser: Codable { // MARK: - Authentication extension BlogUser: Authenticatable { - #warning("this might not need to throw") - func authenticateSession(on req: Request) throws { - try req.session()["_BlogUserSession"] = self.userID?.description + func authenticateSession(on req: Request) { + req.session.data["_BlogUserSession"] = self.userID?.description req.auth.login(self) } } extension Request { - #warning("this might not need to throw") - func unauthenticateBlogUserSession() throws { + func unauthenticateBlogUserSession() { guard self.hasSession else { return } - try session()["_BlogUserSession"] = nil + session.data["_BlogUserSession"] = nil self.auth.logout(BlogUser.self) } } From d9e44ab56b1e978a8045c07c7f1da905aea0f68e Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 13:09:49 +0000 Subject: [PATCH 22/70] Bring part main provider stuff --- Sources/SteamPress/Provider.swift | 141 ++++++++++++++---------------- 1 file changed, 66 insertions(+), 75 deletions(-) diff --git a/Sources/SteamPress/Provider.swift b/Sources/SteamPress/Provider.swift index 7da21fde..9de0ec63 100644 --- a/Sources/SteamPress/Provider.swift +++ b/Sources/SteamPress/Provider.swift @@ -1,42 +1,41 @@ -//import Vapor -//import Authentication -// -//public struct Provider: Vapor.Provider { -// -// let blogPath: String? -// let feedInformation: FeedInformation -// let postsPerPage: Int -// let enableAuthorPages: Bool -// let enableTagPages: Bool -// let pathCreator: BlogPathCreator -// -// /** -// Initialiser for SteamPress' Provider to add a blog to your Vapor App. You can pass it an optional -// `blogPath` to add the blog to. For instance, if you pass in "blog", your blog will be accessible -// at http://mysite.com/blog/, or if you pass in `nil` your blog will be added to the root of your -// site (i.e. http://mysite.com/) -// - Parameter blogPath: The path to add the blog to (see above). -// - Parameter feedInformation: Information to vend to the RSS and Atom feeds. Defaults to empty information. -// - Parameter postsPerPage: The number of posts to show per page on the main index page of the blog. Defaults to 10. -// - Parameter enableAuthorsPages: Flag used to determine whether to publicly expose the authors endpoints -// or not. Defaults to true. -// - Parameter enableTagsPages: Flag used to determine whether to publicy expose the tags endpoints or not. -// Defaults to true. -// */ -// public init( -// blogPath: String? = nil, -// feedInformation: FeedInformation = FeedInformation(), -// postsPerPage: Int = 10, -// enableAuthorPages: Bool = true, -// enableTagPages: Bool = true) { -// self.blogPath = blogPath -// self.feedInformation = feedInformation -// self.postsPerPage = postsPerPage -// self.enableAuthorPages = enableAuthorPages -// self.enableTagPages = enableTagPages -// self.pathCreator = BlogPathCreator(blogPath: self.blogPath) -// } -// +import Vapor + +public struct Provider: LifecycleHandler { + + let blogPath: String? + let feedInformation: FeedInformation + let postsPerPage: Int + let enableAuthorPages: Bool + let enableTagPages: Bool + let pathCreator: BlogPathCreator + + /** + Initialiser for SteamPress' Provider to add a blog to your Vapor App. You can pass it an optional + `blogPath` to add the blog to. For instance, if you pass in "blog", your blog will be accessible + at http://mysite.com/blog/, or if you pass in `nil` your blog will be added to the root of your + site (i.e. http://mysite.com/) + - Parameter blogPath: The path to add the blog to (see above). + - Parameter feedInformation: Information to vend to the RSS and Atom feeds. Defaults to empty information. + - Parameter postsPerPage: The number of posts to show per page on the main index page of the blog. Defaults to 10. + - Parameter enableAuthorsPages: Flag used to determine whether to publicly expose the authors endpoints + or not. Defaults to true. + - Parameter enableTagsPages: Flag used to determine whether to publicy expose the tags endpoints or not. + Defaults to true. + */ + public init( + blogPath: String? = nil, + feedInformation: FeedInformation = FeedInformation(), + postsPerPage: Int = 10, + enableAuthorPages: Bool = true, + enableTagPages: Bool = true) { + self.blogPath = blogPath + self.feedInformation = feedInformation + self.postsPerPage = postsPerPage + self.enableAuthorPages = enableAuthorPages + self.enableTagPages = enableTagPages + self.pathCreator = BlogPathCreator(blogPath: self.blogPath) + } + // public func register(_ services: inout Services) throws { // services.register(BlogPresenter.self) { _ in // return ViewBlogPresenter() @@ -55,40 +54,32 @@ // } // // services.register(BlogRememberMeMiddleware.self) -// services.register(LongPostDateFormatter.self) -// services.register(NumericPostDateFormatter.self) -// } -// -// public func willBoot(_ container: Container) throws -> EventLoopFuture { -// let router = try container.make(Router.self) -// -// let feedController = FeedController(pathCreator: self.pathCreator, feedInformation: self.feedInformation) -// let apiController = APIController() -// let blogController = BlogController(pathCreator: self.pathCreator, enableAuthorPages: self.enableAuthorPages, enableTagPages: self.enableTagPages, postsPerPage: self.postsPerPage) -// let blogAdminController = BlogAdminController(pathCreator: self.pathCreator) -// -// let blogRoutes: RoutesBuilder -// if let blogPath = blogPath { -// blogRoutes = router.grouped(blogPath) -// } else { -// blogRoutes = router.grouped("") -// } -// let steampressSessionsConfig = SessionsConfig(cookieName: "steampress-session") { value in -// return HTTPCookieValue(string: value) -// } -// let steampressSessions = try SessionsMiddleware(sessions: container.make(), config: steampressSessionsConfig) -// let steampressAuthSessions = BlogAuthSessionsMiddleware() -// let sessionedRoutes = blogRoutes.grouped(steampressSessions, steampressAuthSessions) -// -// try sessionedRoutes.register(collection: feedController) -// try sessionedRoutes.register(collection: apiController) -// try sessionedRoutes.register(collection: blogController) -// try sessionedRoutes.register(collection: blogAdminController) -// return .done(on: container) -// } -// -// public func didBoot(_ container: Container) throws -> EventLoopFuture { -// return .done(on: container) // } -// -//} + + public func willBoot(_ application: Application) throws { + let router = application.routes + + let feedController = FeedController(pathCreator: self.pathCreator, feedInformation: self.feedInformation) + let apiController = APIController() + let blogController = BlogController(pathCreator: self.pathCreator, enableAuthorPages: self.enableAuthorPages, enableTagPages: self.enableTagPages, postsPerPage: self.postsPerPage) + let blogAdminController = BlogAdminController(pathCreator: self.pathCreator) + + let blogRoutes: RoutesBuilder + if let blogPath = blogPath { + blogRoutes = router.grouped(PathComponent(stringLiteral: blogPath)) + } else { + blogRoutes = router.grouped("") + } + let steampressSessionsConfig = SessionsConfiguration(cookieName: "steampress-session") { value in + HTTPCookies.Value(string: value.string) + } + let steampressSessions = SessionsMiddleware(session: application.sessions.driver, configuration: steampressSessionsConfig) + let steampressAuthSessions = BlogAuthSessionsMiddleware() + let sessionedRoutes = blogRoutes.grouped(steampressSessions, steampressAuthSessions) + + try sessionedRoutes.register(collection: feedController) + try sessionedRoutes.register(collection: apiController) + try sessionedRoutes.register(collection: blogController) + try sessionedRoutes.register(collection: blogAdminController) + } +} From 1b4be7195683bf93249fb05c679b2c5cea66a098 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 13:10:38 +0000 Subject: [PATCH 23/70] Rename for new protocol --- Sources/SteamPress/{Provider.swift => SteampressLifecyle.swift} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Sources/SteamPress/{Provider.swift => SteampressLifecyle.swift} (98%) diff --git a/Sources/SteamPress/Provider.swift b/Sources/SteamPress/SteampressLifecyle.swift similarity index 98% rename from Sources/SteamPress/Provider.swift rename to Sources/SteamPress/SteampressLifecyle.swift index 9de0ec63..d5977929 100644 --- a/Sources/SteamPress/Provider.swift +++ b/Sources/SteamPress/SteampressLifecyle.swift @@ -1,6 +1,6 @@ import Vapor -public struct Provider: LifecycleHandler { +public struct SteampressLifecyle: LifecycleHandler { let blogPath: String? let feedInformation: FeedInformation From 84a15133883b8e07362f717a37cbd86accec91d6 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 14:18:41 +0000 Subject: [PATCH 24/70] Migrate RNG to real service type --- .../Presenters/BlogAdminPresenter.swift | 2 +- .../SteamPress/Presenters/BlogPresenter.swift | 2 +- .../SteamPressRandomNumberGenerator.swift | 82 +++++++++++++++---- Sources/SteamPress/SteampressLifecyle.swift | 5 ++ 4 files changed, 73 insertions(+), 18 deletions(-) diff --git a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift index 1b8c241d..83db7270 100644 --- a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift @@ -14,7 +14,7 @@ extension Request { } extension Application { - private struct AdminPresenterKey: StorageKey { + struct AdminPresenterKey: StorageKey { typealias Value = AdminPresenterFactory } var adminPresenterFactory: AdminPresenterFactory { diff --git a/Sources/SteamPress/Presenters/BlogPresenter.swift b/Sources/SteamPress/Presenters/BlogPresenter.swift index 54801256..736b8892 100644 --- a/Sources/SteamPress/Presenters/BlogPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogPresenter.swift @@ -18,7 +18,7 @@ extension Request { } extension Application { - private struct BlogPresenterKey: StorageKey { + struct BlogPresenterKey: StorageKey { typealias Value = BlogPresenterFactory } var blogPresenterFactory: BlogPresenterFactory { diff --git a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift index db9e6a61..65b04b65 100644 --- a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift +++ b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift @@ -1,32 +1,82 @@ import Vapor public protocol SteamPressRandomNumberGenerator { + func `for`(_ request: Request) -> SteamPressRandomNumberGenerator func getNumber() -> Int } -private struct RandomNumberGeneratorFactory { - var makeRNG: ((Request) -> SteamPressRandomNumberGenerator)? - mutating func use(_ makeRNG: @escaping (Request) -> SteamPressRandomNumberGenerator) { - self.makeRNG = makeRNG +extension RealRandomNumberGenerator { + public func `for`(_ request: Request) -> SteamPressRandomNumberGenerator { + RealRandomNumberGenerator() } } -private extension Application { - private struct RandomNumberGeneratorKey: StorageKey { - typealias Value = RandomNumberGeneratorFactory +public extension Request { + var randomNumberGenerator: SteamPressRandomNumberGenerator { + self.application.randomNumberGenerators.generator.for(self) } - var randomNumberGenerators: RandomNumberGeneratorFactory { - get { - self.storage[RandomNumberGeneratorKey.self] ?? .init() +} + +extension Application { + public struct RandomNumberGenerators { + public struct Provider { + public static var real: Self { + .init { + $0.randomNumberGenerators.use { $0.randomNumberGenerators.real } + } + } + + let run: (Application) -> () + + public init(_ run: @escaping (Application) -> ()) { + self.run = run + } + } + + final class Storage { + var makeGenerator: ((Application) -> SteamPressRandomNumberGenerator)? + init() { } + } + + struct Key: StorageKey { + typealias Value = Storage + } + + let application: Application + + public var real: RealRandomNumberGenerator { + return .init() } - set { - self.storage[RandomNumberGeneratorKey.self] = newValue + + public var generator: SteamPressRandomNumberGenerator { + guard let makeGenerator = self.storage.makeGenerator else { + fatalError("No random number generator configured. Configure with app.randomNumberGenerators.use(...)") + } + return makeGenerator(self.application) + } + + public func use(_ provider: Provider) { + provider.run(self.application) + } + + public func use(_ makeGenerator: @escaping (Application) -> (SteamPressRandomNumberGenerator)) { + self.storage.makeGenerator = makeGenerator + } + + func initialize() { + self.application.storage[Key.self] = .init() + self.use(.real) + } + + private var storage: Storage { + guard let storage = self.application.storage[Key.self] else { + fatalError("RandomNumberGenerators not configured. Configure with app.randomNumberGenerators.initialize()") + } + return storage } } -} -public extension Request { - var randomNumberGenerator: SteamPressRandomNumberGenerator { - self.application.randomNumberGenerators.makeRNG!(self) + public var randomNumberGenerators: RandomNumberGenerators { + .init(application: self) } } diff --git a/Sources/SteamPress/SteampressLifecyle.swift b/Sources/SteamPress/SteampressLifecyle.swift index d5977929..b78afe5b 100644 --- a/Sources/SteamPress/SteampressLifecyle.swift +++ b/Sources/SteamPress/SteampressLifecyle.swift @@ -57,6 +57,11 @@ public struct SteampressLifecyle: LifecycleHandler { // } public func willBoot(_ application: Application) throws { + +// application.storage[BlogPresenterKey.self] = ViewBlogPresenter(viewRenderer: <#T##ViewRenderer#>, longDateFormatter: <#T##LongPostDateFormatter#>, numericDateFormatter: <#T##NumericPostDateFormatter#>, eventLoopGroup: <#T##EventLoopGroup#>) +// application.storage[RandomNumberGeneratorKey.self] = RealRandomNumberGenerator() + application.randomNumberGenerators.initialize() + let router = application.routes let feedController = FeedController(pathCreator: self.pathCreator, feedInformation: self.feedInformation) From 76d66677a92c150b814763892f4bae9df7ec5bf7 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 14:51:07 +0000 Subject: [PATCH 25/70] Port the rest of the services over --- .../Extensions/BCrypt+PasswordHasher.swift | 162 ++++++++++++++---- .../Presenters/BlogAdminPresenter.swift | 80 +++++++-- .../SteamPress/Presenters/BlogPresenter.swift | 80 +++++++-- .../SteamPressRandomNumberGenerator.swift | 18 +- Sources/SteamPress/SteampressLifecyle.swift | 23 +-- 5 files changed, 273 insertions(+), 90 deletions(-) diff --git a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift index 0fd5bd5b..eb96e9a0 100644 --- a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift +++ b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift @@ -1,17 +1,23 @@ import Vapor import Crypto -public protocol PasswordHasher { +protocol PasswordHasher { + func `for`(_ request: Request) -> PasswordHasher func hash(_ plaintext: String) throws -> String } extension BCryptDigest: PasswordHasher { - public func hash(_ plaintext: String) throws -> String { + func hash(_ plaintext: String) throws -> String { return try self.hash(plaintext) } + + func `for`(_ request: Request) -> PasswordHasher { + return BCryptDigest() + } } protocol SteamPressPasswordVerifier { + func `for`(_ request: Request) -> SteamPressPasswordVerifier func verify(_ plaintext: String, created hash: String) throws -> Bool } @@ -19,55 +25,145 @@ extension BCryptDigest: SteamPressPasswordVerifier { func verify(_ plaintext: String, created hash: String) throws -> Bool { return try self.verify(plaintext, created: hash) } + + func `for`(_ request: Request) -> SteamPressPasswordVerifier { + return BCryptDigest() + } } -public extension Request { +extension Request { var passwordHasher: PasswordHasher { - self.application.passwordHasherFactory.makeHasher!(self) + self.application.passwordHashers.passwordHasher.for(self) } - internal var passwordVerifier: SteamPressPasswordVerifier { - self.application.passwordVerifierFactory.makeVerifier!(self) + var passwordVerifier: SteamPressPasswordVerifier { + self.application.passwordVerifiers.passwordVerifier.for(self) } } -private extension Application { - private struct PasswordHasherKey: StorageKey { - typealias Value = PasswordHasherFactory - } - var passwordHasherFactory: PasswordHasherFactory { - get { - self.storage[PasswordHasherKey.self] ?? .init() +extension Application { + struct PasswordVerifiers { + struct Provider { + static var bcrypt: Self { + .init { + $0.passwordVerifiers.use { $0.passwordVerifiers.bcrypt } + } + } + + let run: (Application) -> () + + init(_ run: @escaping (Application) -> ()) { + self.run = run + } + } + + final class Storage { + var makeVerifier: ((Application) -> SteamPressPasswordVerifier)? + init() { } + } + + struct Key: StorageKey { + typealias Value = Storage + } + + let application: Application + + var bcrypt: BCryptDigest { + return .init() + } + + var passwordVerifier: SteamPressPasswordVerifier { + guard let makeVerifier = self.storage.makeVerifier else { + fatalError("No password verifier configured. Configure with app.passwordVerifiers.use(...)") + } + return makeVerifier(self.application) + } + + func use(_ provider: Provider) { + provider.run(self.application) + } + + func use(_ makeVerifier: @escaping (Application) -> (SteamPressPasswordVerifier)) { + self.storage.makeVerifier = makeVerifier + } + + func initialize() { + self.application.storage[Key.self] = .init() + self.use(.bcrypt) } - set { - self.storage[PasswordHasherKey.self] = newValue + + private var storage: Storage { + guard let storage = self.application.storage[Key.self] else { + fatalError("PasswordVerifiers not configured. Configure with app.passwordVerifiers.initialize()") + } + return storage } } - private struct PasswordVerifierKey: StorageKey { - typealias Value = PasswordVerifierFactory + var passwordVerifiers: PasswordVerifiers { + .init(application: self) } - var passwordVerifierFactory: PasswordVerifierFactory { - get { - self.storage[PasswordVerifierKey.self] ?? .init() + + struct PasswordHashers { + struct Provider { + static var bcrypt: Self { + .init { + $0.passwordHashers.use { $0.passwordHashers.bcrypt } + } + } + + let run: (Application) -> () + + init(_ run: @escaping (Application) -> ()) { + self.run = run + } } - set { - self.storage[PasswordVerifierKey.self] = newValue + + final class Storage { + var makeHasher: ((Application) -> PasswordHasher)? + init() { } + } + + struct Key: StorageKey { + typealias Value = Storage + } + + let application: Application + + var bcrypt: BCryptDigest { + return .init() } - } -} -private struct PasswordHasherFactory { - var makeHasher: ((Request) -> PasswordHasher)? - mutating func use(_ makeHasher: @escaping (Request) -> PasswordHasher) { - self.makeHasher = makeHasher + var passwordHasher: PasswordHasher { + guard let makeHasher = self.storage.makeHasher else { + fatalError("No password hasher configured. Configure with app.passwordHashers.use(...)") + } + return makeHasher(self.application) + } + + func use(_ provider: Provider) { + provider.run(self.application) + } + + func use(_ makeHasher: @escaping (Application) -> (PasswordHasher)) { + self.storage.makeHasher = makeHasher + } + + func initialize() { + self.application.storage[Key.self] = .init() + self.use(.bcrypt) + } + + private var storage: Storage { + guard let storage = self.application.storage[Key.self] else { + fatalError("PasswordHashers not configured. Configure with app.passwordHashers.initialize()") + } + return storage + } } -} -private struct PasswordVerifierFactory { - var makeVerifier: ((Request) -> SteamPressPasswordVerifier)? - mutating func use(_ makeVerifier: @escaping (Request) -> SteamPressPasswordVerifier) { - self.makeVerifier = makeVerifier + var passwordHashers: PasswordHashers { + .init(application: self) } } diff --git a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift index 83db7270..9ac99073 100644 --- a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift @@ -1,35 +1,87 @@ import Vapor public protocol BlogAdminPresenter { + func `for`(_ request: Request) -> BlogAdminPresenter func createIndexView(posts: [BlogPost], users: [BlogUser], errors: [String]?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture func createPostView(errors: [String]?, title: String?, contents: String?, slugURL: String?, tags: [String]?, isEditing: Bool, post: BlogPost?, isDraft: Bool?, titleError: Bool, contentsError: Bool, pageInformation: BlogAdminPageInformation) -> EventLoopFuture func createUserView(editing: Bool, errors: [String]?, name: String?, nameError: Bool, username: String?, usernameErorr: Bool, passwordError: Bool, confirmPasswordError: Bool, resetPasswordOnLogin: Bool, userID: Int?, profilePicture: String?, twitterHandle: String?, biography: String?, tagline: String?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture func createResetPasswordView(errors: [String]?, passwordError: Bool?, confirmPasswordError: Bool?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture } +extension ViewBlogAdminPresenter { + public func `for`(_ request: Request) -> BlogAdminPresenter { + #warning("TODO path create") + return ViewBlogAdminPresenter(pathCreator: BlogPathCreator(blogPath: nil), viewRenderer: request.view, eventLoopGroup: request.eventLoop, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) + } +} + extension Request { var adminPresenter: BlogAdminPresenter { - self.application.adminPresenterFactory.makePresenter!(self) + self.application.adminPresenters.adminPresenter.for(self) } } extension Application { - struct AdminPresenterKey: StorageKey { - typealias Value = AdminPresenterFactory - } - var adminPresenterFactory: AdminPresenterFactory { - get { - self.storage[AdminPresenterKey.self] ?? .init() + struct BlogAdminPresenters { + struct Provider { + static var view: Self { + .init { + $0.adminPresenters.use { $0.adminPresenters.view } + } + } + + let run: (Application) -> () + + init(_ run: @escaping (Application) -> ()) { + self.run = run + } + } + + final class Storage { + var makePresenter: ((Application) -> BlogAdminPresenter)? + init() { } + } + + struct Key: StorageKey { + typealias Value = Storage + } + + let application: Application + + var view: ViewBlogAdminPresenter { + #warning("Sort out Blog Path Creator") + return .init(pathCreator: BlogPathCreator(blogPath: nil), viewRenderer: self.application.views.renderer, eventLoopGroup: self.application.eventLoopGroup, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) + } + + var adminPresenter: BlogAdminPresenter { + guard let makePresenter = self.storage.makePresenter else { + fatalError("No blog admin presenter configured. Configure with app.adminPresenters.use(...)") + } + return makePresenter(self.application) } - set { - self.storage[AdminPresenterKey.self] = newValue + + func use(_ provider: Provider) { + provider.run(self.application) + } + + func use(_ makePresenter: @escaping (Application) -> (BlogAdminPresenter)) { + self.storage.makePresenter = makePresenter + } + + func initialize() { + self.application.storage[Key.self] = .init() + self.use(.view) + } + + private var storage: Storage { + guard let storage = self.application.storage[Key.self] else { + fatalError("BlogAdminPresenters not configured. Configure with app.adminPresenters.initialize()") + } + return storage } } -} -struct AdminPresenterFactory { - var makePresenter: ((Request) -> BlogAdminPresenter)? - mutating func use(_ makePresenter: @escaping (Request) -> BlogAdminPresenter) { - self.makePresenter = makePresenter + var adminPresenters: BlogAdminPresenters { + .init(application: self) } } diff --git a/Sources/SteamPress/Presenters/BlogPresenter.swift b/Sources/SteamPress/Presenters/BlogPresenter.swift index 736b8892..d88eaf8e 100644 --- a/Sources/SteamPress/Presenters/BlogPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogPresenter.swift @@ -1,6 +1,7 @@ import Vapor public protocol BlogPresenter { + func `for`(_ request: Request) -> BlogPresenter func indexView(posts: [BlogPost], tags: [BlogTag], authors: [BlogUser], tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture func postView(post: BlogPost, author: BlogUser, tags: [BlogTag], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture func allAuthorsView(authors: [BlogUser], authorPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture @@ -11,29 +12,78 @@ public protocol BlogPresenter { func loginView(loginWarning: Bool, errors: [String]?, username: String?, usernameError: Bool, passwordError: Bool, rememberMe: Bool, pageInformation: BlogGlobalPageInformation) -> EventLoopFuture } -extension Request { +extension ViewBlogPresenter { + public func `for`(_ request: Request) -> BlogPresenter { + return ViewBlogPresenter(viewRenderer: request.view, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: request.eventLoop) + } +} + +public extension Request { var blogPresenter: BlogPresenter { - self.application.blogPresenterFactory.makePresenter!(self) + self.application.blogPresenters.blogPresenter.for(self) } } extension Application { - struct BlogPresenterKey: StorageKey { - typealias Value = BlogPresenterFactory - } - var blogPresenterFactory: BlogPresenterFactory { - get { - self.storage[BlogPresenterKey.self] ?? .init() + struct BlogPresenters { + struct Provider { + static var view: Self { + .init { + $0.blogPresenters.use { $0.blogPresenters.view } + } + } + + let run: (Application) -> () + + init(_ run: @escaping (Application) -> ()) { + self.run = run + } + } + + final class Storage { + var makePresenter: ((Application) -> BlogPresenter)? + init() { } + } + + struct Key: StorageKey { + typealias Value = Storage + } + + let application: Application + + var view: ViewBlogPresenter { + return .init(viewRenderer: self.application.views.renderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: self.application.eventLoopGroup) + } + + var blogPresenter: BlogPresenter { + guard let makePresenter = self.storage.makePresenter else { + fatalError("No blog presenter configured. Configure with app.blogPresenters.use(...)") + } + return makePresenter(self.application) } - set { - self.storage[BlogPresenterKey.self] = newValue + + func use(_ provider: Provider) { + provider.run(self.application) + } + + func use(_ makePresenter: @escaping (Application) -> (BlogPresenter)) { + self.storage.makePresenter = makePresenter + } + + func initialize() { + self.application.storage[Key.self] = .init() + self.use(.view) + } + + private var storage: Storage { + guard let storage = self.application.storage[Key.self] else { + fatalError("BlogPresenters not configured. Configure with app.blogPresenters.initialize()") + } + return storage } } -} -struct BlogPresenterFactory { - var makePresenter: ((Request) -> BlogPresenter)? - mutating func use(_ makePresenter: @escaping (Request) -> BlogPresenter) { - self.makePresenter = makePresenter + var blogPresenters: BlogPresenters { + .init(application: self) } } diff --git a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift index 65b04b65..eedf7e50 100644 --- a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift +++ b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift @@ -18,9 +18,9 @@ public extension Request { } extension Application { - public struct RandomNumberGenerators { - public struct Provider { - public static var real: Self { + struct RandomNumberGenerators { + struct Provider { + static var real: Self { .init { $0.randomNumberGenerators.use { $0.randomNumberGenerators.real } } @@ -28,7 +28,7 @@ extension Application { let run: (Application) -> () - public init(_ run: @escaping (Application) -> ()) { + init(_ run: @escaping (Application) -> ()) { self.run = run } } @@ -44,22 +44,22 @@ extension Application { let application: Application - public var real: RealRandomNumberGenerator { + var real: RealRandomNumberGenerator { return .init() } - public var generator: SteamPressRandomNumberGenerator { + var generator: SteamPressRandomNumberGenerator { guard let makeGenerator = self.storage.makeGenerator else { fatalError("No random number generator configured. Configure with app.randomNumberGenerators.use(...)") } return makeGenerator(self.application) } - public func use(_ provider: Provider) { + func use(_ provider: Provider) { provider.run(self.application) } - public func use(_ makeGenerator: @escaping (Application) -> (SteamPressRandomNumberGenerator)) { + func use(_ makeGenerator: @escaping (Application) -> (SteamPressRandomNumberGenerator)) { self.storage.makeGenerator = makeGenerator } @@ -76,7 +76,7 @@ extension Application { } } - public var randomNumberGenerators: RandomNumberGenerators { + var randomNumberGenerators: RandomNumberGenerators { .init(application: self) } } diff --git a/Sources/SteamPress/SteampressLifecyle.swift b/Sources/SteamPress/SteampressLifecyle.swift index b78afe5b..1551a24f 100644 --- a/Sources/SteamPress/SteampressLifecyle.swift +++ b/Sources/SteamPress/SteampressLifecyle.swift @@ -37,30 +37,15 @@ public struct SteampressLifecyle: LifecycleHandler { } // public func register(_ services: inout Services) throws { -// services.register(BlogPresenter.self) { _ in -// return ViewBlogPresenter() -// } -// -// services.register(BlogAdminPresenter.self) { _ in -// return ViewBlogAdminPresenter(pathCreator: self.pathCreator) -// } -// -// try services.register(AuthenticationProvider()) -// services.register([PasswordHasher.self, PasswordVerifier.self]) { _ in -// return BCryptDigest() -// } -// services.register(SteamPressRandomNumberGenerator.self) { _ in -// return RealRandomNumberGenerator() -// } -// // services.register(BlogRememberMeMiddleware.self) // } public func willBoot(_ application: Application) throws { - -// application.storage[BlogPresenterKey.self] = ViewBlogPresenter(viewRenderer: <#T##ViewRenderer#>, longDateFormatter: <#T##LongPostDateFormatter#>, numericDateFormatter: <#T##NumericPostDateFormatter#>, eventLoopGroup: <#T##EventLoopGroup#>) -// application.storage[RandomNumberGeneratorKey.self] = RealRandomNumberGenerator() application.randomNumberGenerators.initialize() + application.blogPresenters.initialize() + application.adminPresenters.initialize() + application.passwordHashers.initialize() + application.passwordVerifiers.initialize() let router = application.routes From 373fe7e0638ed5b60a53b3b495944e4a26513d48 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 27 Mar 2020 14:55:56 +0000 Subject: [PATCH 26/70] Fix the path creator issue --- .../Presenters/BlogAdminPresenter.swift | 25 ++++++++++--------- Sources/SteamPress/SteampressLifecyle.swift | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift index 9ac99073..b603f03d 100644 --- a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift @@ -1,7 +1,7 @@ import Vapor -public protocol BlogAdminPresenter { - func `for`(_ request: Request) -> BlogAdminPresenter +protocol BlogAdminPresenter { + func `for`(_ request: Request, pathCreator: BlogPathCreator) -> BlogAdminPresenter func createIndexView(posts: [BlogPost], users: [BlogUser], errors: [String]?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture func createPostView(errors: [String]?, title: String?, contents: String?, slugURL: String?, tags: [String]?, isEditing: Bool, post: BlogPost?, isDraft: Bool?, titleError: Bool, contentsError: Bool, pageInformation: BlogAdminPageInformation) -> EventLoopFuture func createUserView(editing: Bool, errors: [String]?, name: String?, nameError: Bool, username: String?, usernameErorr: Bool, passwordError: Bool, confirmPasswordError: Bool, resetPasswordOnLogin: Bool, userID: Int?, profilePicture: String?, twitterHandle: String?, biography: String?, tagline: String?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture @@ -9,15 +9,14 @@ public protocol BlogAdminPresenter { } extension ViewBlogAdminPresenter { - public func `for`(_ request: Request) -> BlogAdminPresenter { - #warning("TODO path create") - return ViewBlogAdminPresenter(pathCreator: BlogPathCreator(blogPath: nil), viewRenderer: request.view, eventLoopGroup: request.eventLoop, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) + func `for`(_ request: Request, pathCreator: BlogPathCreator) -> BlogAdminPresenter { + return ViewBlogAdminPresenter(pathCreator: pathCreator, viewRenderer: request.view, eventLoopGroup: request.eventLoop, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) } } extension Request { var adminPresenter: BlogAdminPresenter { - self.application.adminPresenters.adminPresenter.for(self) + self.application.adminPresenters.adminPresenter.for(self, pathCreator: self.application.adminPresenters.storage.pathCreator) } } @@ -38,8 +37,11 @@ extension Application { } final class Storage { + let pathCreator: BlogPathCreator var makePresenter: ((Application) -> BlogAdminPresenter)? - init() { } + init(pathCreator: BlogPathCreator) { + self.pathCreator = pathCreator + } } struct Key: StorageKey { @@ -49,8 +51,7 @@ extension Application { let application: Application var view: ViewBlogAdminPresenter { - #warning("Sort out Blog Path Creator") - return .init(pathCreator: BlogPathCreator(blogPath: nil), viewRenderer: self.application.views.renderer, eventLoopGroup: self.application.eventLoopGroup, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) + return .init(pathCreator: self.storage.pathCreator, viewRenderer: self.application.views.renderer, eventLoopGroup: self.application.eventLoopGroup, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) } var adminPresenter: BlogAdminPresenter { @@ -68,12 +69,12 @@ extension Application { self.storage.makePresenter = makePresenter } - func initialize() { - self.application.storage[Key.self] = .init() + func initialize(pathCreator: BlogPathCreator) { + self.application.storage[Key.self] = .init(pathCreator: pathCreator) self.use(.view) } - private var storage: Storage { + var storage: Storage { guard let storage = self.application.storage[Key.self] else { fatalError("BlogAdminPresenters not configured. Configure with app.adminPresenters.initialize()") } diff --git a/Sources/SteamPress/SteampressLifecyle.swift b/Sources/SteamPress/SteampressLifecyle.swift index 1551a24f..04c79485 100644 --- a/Sources/SteamPress/SteampressLifecyle.swift +++ b/Sources/SteamPress/SteampressLifecyle.swift @@ -43,7 +43,7 @@ public struct SteampressLifecyle: LifecycleHandler { public func willBoot(_ application: Application) throws { application.randomNumberGenerators.initialize() application.blogPresenters.initialize() - application.adminPresenters.initialize() + application.adminPresenters.initialize(pathCreator: pathCreator) application.passwordHashers.initialize() application.passwordVerifiers.initialize() From 994d0ac13f00e26d2df0593ea66eeef56e13e4ad Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 3 Apr 2020 16:27:44 +0100 Subject: [PATCH 27/70] Start porting the tests --- Package.swift | 3 +- .../AdminTests/AccessControlTests.swift | 16 +- .../BlogTests/SearchTests.swift | 158 +++++++++--------- .../SteamPressTests/BlogTests/TagTests.swift | 2 +- .../Fakes/CapturingViewRenderer.swift | 34 ++-- .../Fakes/PlaintextHasher.swift | 10 +- .../Fakes/StubbedRandomNumberGenerator.swift | 9 + .../Feed Tests/AtomFeedTests.swift | 2 +- .../Feed Tests/RSSFeedTests.swift | 2 +- 9 files changed, 122 insertions(+), 114 deletions(-) diff --git a/Package.swift b/Package.swift index 47e3b607..0e0896f9 100644 --- a/Package.swift +++ b/Package.swift @@ -21,8 +21,7 @@ let package = Package( .product(name: "Vapor", package: "vapor"), .product(name: "LeafKit", package: "leaf-kit"), "SwiftSoup", - "SwiftMarkdown", -// .product(name: "Authentication", package: "vapor") + "SwiftMarkdown" ]), .testTarget(name: "SteamPressTests", dependencies: ["SteamPress"]), ] diff --git a/Tests/SteamPressTests/AdminTests/AccessControlTests.swift b/Tests/SteamPressTests/AdminTests/AccessControlTests.swift index e3e58131..96a787d2 100644 --- a/Tests/SteamPressTests/AdminTests/AccessControlTests.swift +++ b/Tests/SteamPressTests/AdminTests/AccessControlTests.swift @@ -82,33 +82,33 @@ class AccessControlTests: XCTestCase { func testCanAccessAdminPageWhenLoggedIn() throws { let response = try testWorld.getResponse(to: "/blog/admin/", loggedInUser: user) - XCTAssertEqual(response.http.status, .ok) + XCTAssertEqual(response.status, .ok) } func testCanAccessCreatePostPageWhenLoggedIn() throws { let response = try testWorld.getResponse(to: "/blog/admin/createPost", loggedInUser: user) - XCTAssertEqual(response.http.status, .ok) + XCTAssertEqual(response.status, .ok) } func testCanAccessEditPostPageWhenLoggedIn() throws { let post = try testWorld.createPost() let response = try testWorld.getResponse(to: "/blog/admin/posts/\(post.post.blogID!)/edit", loggedInUser: user) - XCTAssertEqual(response.http.status, .ok) + XCTAssertEqual(response.status, .ok) } func testCanAccessCreateUserPageWhenLoggedIn() throws { let response = try testWorld.getResponse(to: "/blog/admin/createUser", loggedInUser: user) - XCTAssertEqual(response.http.status, .ok) + XCTAssertEqual(response.status, .ok) } func testCanAccessEditUserPageWhenLoggedIn() throws { let response = try testWorld.getResponse(to: "/blog/admin/users/1/edit", loggedInUser: user) - XCTAssertEqual(response.http.status, .ok) + XCTAssertEqual(response.status, .ok) } func testCanAccessResetPasswordPage() throws { let response = try testWorld.getResponse(to: "/blog/admin/resetPassword", loggedInUser: user) - XCTAssertEqual(response.http.status, .ok) + XCTAssertEqual(response.status, .ok) } // MARK: - Helpers @@ -116,8 +116,8 @@ class AccessControlTests: XCTestCase { private func assertLoginRequired(method: HTTPMethod, path: String) throws { let response = try testWorld.getResponse(to: "/blog/admin/\(path)", method: method) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/blog/admin/login/?loginRequired") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/blog/admin/login/?loginRequired") } } diff --git a/Tests/SteamPressTests/BlogTests/SearchTests.swift b/Tests/SteamPressTests/BlogTests/SearchTests.swift index d8f642f8..916d9480 100644 --- a/Tests/SteamPressTests/BlogTests/SearchTests.swift +++ b/Tests/SteamPressTests/BlogTests/SearchTests.swift @@ -26,83 +26,83 @@ class SearchTests: XCTestCase { // MARK: - Tests - func testBlogPassedToSearchPageCorrectly() throws { - let response = try testWorld.getResponse(to: "/search?term=Test") - - XCTAssertEqual(response.http.status, .ok) - XCTAssertEqual(presenter.searchTerm, "Test") - XCTAssertEqual(presenter.searchTotalResults, 1) - XCTAssertEqual(presenter.searchPosts?.first?.title, firstData.post.title) - } - - func testThatSearchTermNilIfEmptySearch() throws { - let response = try testWorld.getResponse(to: "/search?term=") - - XCTAssertEqual(response.http.status, .ok) - XCTAssertEqual(presenter.searchPosts?.count, 0) - XCTAssertNil(presenter.searchTerm) - } - - func testThatSearchTermNilIfNoSearchTerm() throws { - let response = try testWorld.getResponse(to: "/search") - - XCTAssertEqual(response.http.status, .ok) - XCTAssertEqual(presenter.searchPosts?.count, 0) - XCTAssertNil(presenter.searchTerm) - } - - func testCorrectPageInformationForSearch() throws { - _ = try testWorld.getResponse(to: "/search?term=Test") - XCTAssertNil(presenter.searchPageInformation?.disqusName) - XCTAssertNil(presenter.searchPageInformation?.googleAnalyticsIdentifier) - XCTAssertNil(presenter.searchPageInformation?.siteTwitterHandle) - XCTAssertNil(presenter.searchPageInformation?.loggedInUser) - XCTAssertEqual(presenter.searchPageInformation?.currentPageURL.absoluteString, "/search") - XCTAssertEqual(presenter.searchPageInformation?.websiteURL.absoluteString, "/") - } - - func testPageInformationGetsLoggedInUserForSearch() throws { - _ = try testWorld.getResponse(to: "/search?term=Test", loggedInUser: firstData.author) - XCTAssertEqual(presenter.searchPageInformation?.loggedInUser?.username, firstData.author.username) - } - - func testSettingEnvVarsWithPageInformationForSearch() throws { - let googleAnalytics = "ABDJIODJWOIJIWO" - let twitterHandle = "3483209fheihgifffe" - let disqusName = "34829u48932fgvfbrtewerg" - setenv("BLOG_GOOGLE_ANALYTICS_IDENTIFIER", googleAnalytics, 1) - setenv("BLOG_SITE_TWITTER_HANDLE", twitterHandle, 1) - setenv("BLOG_DISQUS_NAME", disqusName, 1) - _ = try testWorld.getResponse(to: "/search?term=Test") - XCTAssertEqual(presenter.searchPageInformation?.disqusName, disqusName) - XCTAssertEqual(presenter.searchPageInformation?.googleAnalyticsIdentifier, googleAnalytics) - XCTAssertEqual(presenter.searchPageInformation?.siteTwitterHandle, twitterHandle) - } - - func testPaginationInfoSetCorrectly() throws { - try testWorld.createPosts(count: 15, author: firstData.author) - _ = try testWorld.getResponse(to: "/search?term=Test&page=1") - XCTAssertEqual(presenter.searchPaginationTagInfo?.currentPage, 1) - XCTAssertEqual(presenter.searchPaginationTagInfo?.totalPages, 1) - XCTAssertEqual(presenter.searchPaginationTagInfo?.currentQuery, "term=Test&page=1") - } - - func testTagsForSearchPostsSetCorrectly() throws { - let post2 = try testWorld.createPost(title: "Test Search", author: firstData.author) - let post3 = try testWorld.createPost(title: "Test Tags", author: firstData.author) - let tag1Name = "Testing" - let tag2Name = "Search" - let tag1 = try testWorld.createTag(tag1Name, on: post2.post) - _ = try testWorld.createTag(tag2Name, on: firstData.post) - try testWorld.context.repository.add(tag1, to: firstData.post) - - _ = try testWorld.getResponse(to: "/search?term=Test") - let tagsForPosts = try XCTUnwrap(presenter.searchPageTagsForPost) - XCTAssertNil(tagsForPosts[post3.post.blogID!]) - XCTAssertEqual(tagsForPosts[post2.post.blogID!]?.count, 1) - XCTAssertEqual(tagsForPosts[post2.post.blogID!]?.first?.name, tag1Name) - XCTAssertEqual(tagsForPosts[firstData.post.blogID!]?.count, 2) - XCTAssertEqual(tagsForPosts[firstData.post.blogID!]?.first?.name, tag1Name) - XCTAssertEqual(tagsForPosts[firstData.post.blogID!]?.last?.name, tag2Name) - } +// func testBlogPassedToSearchPageCorrectly() throws { +// let response = try testWorld.getResponse(to: "/search?term=Test") +// +// XCTAssertEqual(response.http.status, .ok) +// XCTAssertEqual(presenter.searchTerm, "Test") +// XCTAssertEqual(presenter.searchTotalResults, 1) +// XCTAssertEqual(presenter.searchPosts?.first?.title, firstData.post.title) +// } +// +// func testThatSearchTermNilIfEmptySearch() throws { +// let response = try testWorld.getResponse(to: "/search?term=") +// +// XCTAssertEqual(response.http.status, .ok) +// XCTAssertEqual(presenter.searchPosts?.count, 0) +// XCTAssertNil(presenter.searchTerm) +// } +// +// func testThatSearchTermNilIfNoSearchTerm() throws { +// let response = try testWorld.getResponse(to: "/search") +// +// XCTAssertEqual(response.http.status, .ok) +// XCTAssertEqual(presenter.searchPosts?.count, 0) +// XCTAssertNil(presenter.searchTerm) +// } +// +// func testCorrectPageInformationForSearch() throws { +// _ = try testWorld.getResponse(to: "/search?term=Test") +// XCTAssertNil(presenter.searchPageInformation?.disqusName) +// XCTAssertNil(presenter.searchPageInformation?.googleAnalyticsIdentifier) +// XCTAssertNil(presenter.searchPageInformation?.siteTwitterHandle) +// XCTAssertNil(presenter.searchPageInformation?.loggedInUser) +// XCTAssertEqual(presenter.searchPageInformation?.currentPageURL.absoluteString, "/search") +// XCTAssertEqual(presenter.searchPageInformation?.websiteURL.absoluteString, "/") +// } +// +// func testPageInformationGetsLoggedInUserForSearch() throws { +// _ = try testWorld.getResponse(to: "/search?term=Test", loggedInUser: firstData.author) +// XCTAssertEqual(presenter.searchPageInformation?.loggedInUser?.username, firstData.author.username) +// } +// +// func testSettingEnvVarsWithPageInformationForSearch() throws { +// let googleAnalytics = "ABDJIODJWOIJIWO" +// let twitterHandle = "3483209fheihgifffe" +// let disqusName = "34829u48932fgvfbrtewerg" +// setenv("BLOG_GOOGLE_ANALYTICS_IDENTIFIER", googleAnalytics, 1) +// setenv("BLOG_SITE_TWITTER_HANDLE", twitterHandle, 1) +// setenv("BLOG_DISQUS_NAME", disqusName, 1) +// _ = try testWorld.getResponse(to: "/search?term=Test") +// XCTAssertEqual(presenter.searchPageInformation?.disqusName, disqusName) +// XCTAssertEqual(presenter.searchPageInformation?.googleAnalyticsIdentifier, googleAnalytics) +// XCTAssertEqual(presenter.searchPageInformation?.siteTwitterHandle, twitterHandle) +// } +// +// func testPaginationInfoSetCorrectly() throws { +// try testWorld.createPosts(count: 15, author: firstData.author) +// _ = try testWorld.getResponse(to: "/search?term=Test&page=1") +// XCTAssertEqual(presenter.searchPaginationTagInfo?.currentPage, 1) +// XCTAssertEqual(presenter.searchPaginationTagInfo?.totalPages, 1) +// XCTAssertEqual(presenter.searchPaginationTagInfo?.currentQuery, "term=Test&page=1") +// } +// +// func testTagsForSearchPostsSetCorrectly() throws { +// let post2 = try testWorld.createPost(title: "Test Search", author: firstData.author) +// let post3 = try testWorld.createPost(title: "Test Tags", author: firstData.author) +// let tag1Name = "Testing" +// let tag2Name = "Search" +// let tag1 = try testWorld.createTag(tag1Name, on: post2.post) +// _ = try testWorld.createTag(tag2Name, on: firstData.post) +// try testWorld.context.repository.add(tag1, to: firstData.post) +// +// _ = try testWorld.getResponse(to: "/search?term=Test") +// let tagsForPosts = try XCTUnwrap(presenter.searchPageTagsForPost) +// XCTAssertNil(tagsForPosts[post3.post.blogID!]) +// XCTAssertEqual(tagsForPosts[post2.post.blogID!]?.count, 1) +// XCTAssertEqual(tagsForPosts[post2.post.blogID!]?.first?.name, tag1Name) +// XCTAssertEqual(tagsForPosts[firstData.post.blogID!]?.count, 2) +// XCTAssertEqual(tagsForPosts[firstData.post.blogID!]?.first?.name, tag1Name) +// XCTAssertEqual(tagsForPosts[firstData.post.blogID!]?.last?.name, tag2Name) +// } } diff --git a/Tests/SteamPressTests/BlogTests/TagTests.swift b/Tests/SteamPressTests/BlogTests/TagTests.swift index 1cef5079..d1867488 100644 --- a/Tests/SteamPressTests/BlogTests/TagTests.swift +++ b/Tests/SteamPressTests/BlogTests/TagTests.swift @@ -77,7 +77,7 @@ class TagTests: XCTestCase { func testRequestToURLEncodedTag() throws { _ = try testWorld.createTag("Some tag") let response = try testWorld.getResponse(to: "/tags/Some%20tag") - XCTAssertEqual(response.http.status, .ok) + XCTAssertEqual(response.status, .ok) } func testTagPageInformationGetsLoggedInUser() throws { diff --git a/Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift b/Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift index c44aa725..618dc504 100644 --- a/Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift +++ b/Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift @@ -1,19 +1,19 @@ import Vapor -class CapturingViewRenderer: ViewRenderer, Service { - var shouldCache = false - var worker: Worker - - init(worker: Worker) { - self.worker = worker - } - - private(set) var capturedContext: Encodable? - private(set) var templatePath: String? - func render(_ path: String, _ context: E, userInfo: [AnyHashable: Any]) -> EventLoopFuture where E: Encodable { - self.capturedContext = context - self.templatePath = path - return Future.map(on: worker) { return View(data: "Test".convertToData()) } - } - -} +//class CapturingViewRenderer: ViewRenderer, Service { +// var shouldCache = false +// var worker: Worker +// +// init(worker: Worker) { +// self.worker = worker +// } +// +// private(set) var capturedContext: Encodable? +// private(set) var templatePath: String? +// func render(_ path: String, _ context: E, userInfo: [AnyHashable: Any]) -> EventLoopFuture where E: Encodable { +// self.capturedContext = context +// self.templatePath = path +// return Future.map(on: worker) { return View(data: "Test".convertToData()) } +// } +// +//} diff --git a/Tests/SteamPressTests/Fakes/PlaintextHasher.swift b/Tests/SteamPressTests/Fakes/PlaintextHasher.swift index 50e4afc9..ead4ed0f 100644 --- a/Tests/SteamPressTests/Fakes/PlaintextHasher.swift +++ b/Tests/SteamPressTests/Fakes/PlaintextHasher.swift @@ -1,8 +1,8 @@ import Vapor import SteamPress -struct PlaintextHasher: PasswordHasher { - func hash(_ plaintext: LosslessDataConvertible) throws -> String { - return String.convertFromData(plaintext.convertToData()) - } -} +//struct PlaintextHasher: PasswordHasher { +// func hash(_ plaintext: LosslessDataConvertible) throws -> String { +// return String.convertFromData(plaintext.convertToData()) +// } +//} diff --git a/Tests/SteamPressTests/Fakes/StubbedRandomNumberGenerator.swift b/Tests/SteamPressTests/Fakes/StubbedRandomNumberGenerator.swift index 1b3fb471..18a03431 100644 --- a/Tests/SteamPressTests/Fakes/StubbedRandomNumberGenerator.swift +++ b/Tests/SteamPressTests/Fakes/StubbedRandomNumberGenerator.swift @@ -1,7 +1,16 @@ import SteamPress +import Vapor struct StubbedRandomNumberGenerator: SteamPressRandomNumberGenerator { + func `for`(_ request: Request) -> SteamPressRandomNumberGenerator { + return SteamPressRandomNumberGenerator(numberToReturn: self.numberToReturn) + } + let numberToReturn: Int + + init(numberToReturn: Int) { + self.numberToReturn = numberToReturn + } func getNumber() -> Int { return numberToReturn diff --git a/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift b/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift index 40822ea6..6bdcdd7b 100644 --- a/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift +++ b/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift @@ -216,7 +216,7 @@ class AtomFeedTests: XCTestCase { func testCorrectHeaderSetForAtomFeed() throws { testWorld = try TestWorld.create() let actualXmlResponse = try testWorld.getResponse(to: atomPath) - XCTAssertEqual(actualXmlResponse.http.headers.firstValue(name: .contentType), "application/atom+xml") + XCTAssertEqual(actualXmlResponse.headers.firstValue(name: .contentType), "application/atom+xml") } func testThatDateFormatterIsCorrect() throws { diff --git a/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift b/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift index ef031031..aeafacb7 100644 --- a/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift +++ b/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift @@ -187,7 +187,7 @@ class RSSFeedTests: XCTestCase { testWorld = try TestWorld.create() let actualXmlResponse = try testWorld.getResponse(to: rssPath) - XCTAssertEqual(actualXmlResponse.http.headers.firstValue(name: .contentType), "application/rss+xml") + XCTAssertEqual(actualXmlResponse.headers.firstValue(name: .contentType), "application/rss+xml") } func testThatDateFormatterIsCorrect() throws { From 6185c40b21aa871c4dbe8024fb5d4cc400910ca9 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 3 Apr 2020 16:36:10 +0100 Subject: [PATCH 28/70] Migrate MediaType to HTTPMediaType and all calls to request.http to request --- .../AdminTests/AdminPostTests.swift | 58 +++---- .../AdminTests/AdminUserTests.swift | 96 +++++------ .../AdminTests/LoginTests.swift | 56 +++---- .../BlogTests/AuthorTests.swift | 4 +- .../BlogTests/DisabledBlogTagTests.swift | 4 +- .../BlogTests/IndexTests.swift | 8 +- .../BlogTests/SearchTests.swift | 158 +++++++++--------- .../Helpers/TestWorld+Responses.swift | 4 +- 8 files changed, 194 insertions(+), 194 deletions(-) diff --git a/Tests/SteamPressTests/AdminTests/AdminPostTests.swift b/Tests/SteamPressTests/AdminTests/AdminPostTests.swift index 1471ba60..9f99fd79 100644 --- a/Tests/SteamPressTests/AdminTests/AdminPostTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminPostTests.swift @@ -28,7 +28,7 @@ class AdminPostTests: XCTestCase { func testPostCanBeCreated() throws { struct CreatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] @@ -53,8 +53,8 @@ class AdminPostTests: XCTestCase { XCTAssertTrue(testWorld.context.repository.postTagLinks .contains { $0.postID == post.blogID! && $0.tagID == secondTagID }) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/posts/post-title/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/posts/post-title/") } func testCreatingPostWithNonUniqueSlugFromSameTitle() throws { @@ -63,7 +63,7 @@ class AdminPostTests: XCTestCase { let initialPostData = try testWorld.createPost(title: "Post Title", slugUrl: "post-title") struct CreatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] @@ -75,7 +75,7 @@ class AdminPostTests: XCTestCase { XCTAssertEqual(testWorld.context.repository.posts.count, 2) let post = try XCTUnwrap(testWorld.context.repository.posts.last) XCTAssertEqual(post.slugUrl, "post-title-\(randomNumber)") - XCTAssertEqual(response.http.headers[.location].first, "/posts/post-title-\(randomNumber)/") + XCTAssertEqual(response.headers[.location].first, "/posts/post-title-\(randomNumber)/") } func testPostCreationPageGetsBasicInfo() throws { @@ -101,7 +101,7 @@ class AdminPostTests: XCTestCase { func testPostCannotBeCreatedIfDraftAndPublishNotSet() throws { struct CreatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] @@ -110,12 +110,12 @@ class AdminPostTests: XCTestCase { let response = try testWorld.getResponse(to: createPostPath, body: createData, loggedInUser: user) - XCTAssertEqual(response.http.status, .badRequest) + XCTAssertEqual(response.status, .badRequest) } func testCreatePostMustIncludeTitle() throws { struct CreatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] let publish = true @@ -136,7 +136,7 @@ class AdminPostTests: XCTestCase { func testCreatePostMustIncludeContents() throws { struct CreatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let tags = ["First Tag", "Second Tag"] let publish = true @@ -154,7 +154,7 @@ class AdminPostTests: XCTestCase { func testPresenterGetsDataIfValidationOfDataFails() throws { struct CreatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let tags = ["First Tag", "Second Tag"] let publish = true @@ -176,7 +176,7 @@ class AdminPostTests: XCTestCase { func testCreatePostWithDraftDoesNotPublishPost() throws { struct CreatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] @@ -197,7 +197,7 @@ class AdminPostTests: XCTestCase { let existingTag = try testWorld.createTag(existingTagName, on: existingPost.post) struct CreatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] @@ -218,7 +218,7 @@ class AdminPostTests: XCTestCase { func testPostCanBeUpdated() throws { struct UpdatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] @@ -241,7 +241,7 @@ class AdminPostTests: XCTestCase { func testPostCanBeUpdatedAndUpdateSlugURL() throws { struct UpdatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] @@ -298,8 +298,8 @@ class AdminPostTests: XCTestCase { let updateData = UpdateData(title: testData.post.title) let response = try testWorld.getResponse(to: "/admin/posts/\(testData.post.blogID!)/edit", body: updateData, loggedInUser: user) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/posts/\(testData.post.slugUrl)/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/posts/\(testData.post.slugUrl)/") } func testThatEditingPostGetsRedirectToPostPageWithNewSlugURL() throws { @@ -315,8 +315,8 @@ class AdminPostTests: XCTestCase { let updateData = UpdateData(title: "Some New Title") let response = try testWorld.getResponse(to: "/admin/posts/\(testData.post.blogID!)/edit", body: updateData, loggedInUser: user) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/posts/some-new-title/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/posts/some-new-title/") } func testEditingPostWithNewTagsRemovesOldLinksAndAddsNewLinks() throws { @@ -329,7 +329,7 @@ class AdminPostTests: XCTestCase { let newTagName = "A New Tag" struct UpdatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let contents = "# Post Title\n\nWe have a post" let tags: [String] @@ -352,7 +352,7 @@ class AdminPostTests: XCTestCase { func testLastUpdatedTimeGetsChangedWhenEditingAPost() throws { struct UpdatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] @@ -373,7 +373,7 @@ class AdminPostTests: XCTestCase { func testCreatedTimeSetWhenPublishingADraft() throws { struct UpdatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] @@ -395,7 +395,7 @@ class AdminPostTests: XCTestCase { func testCreatedTimeSetAndMarkedAsDraftWhenSavingADraft() throws { struct UpdatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] @@ -417,7 +417,7 @@ class AdminPostTests: XCTestCase { func testEditingPageWithInvalidDataPassesExistingDataToPresenter() throws { struct UpdatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "" let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] @@ -449,7 +449,7 @@ class AdminPostTests: XCTestCase { func testEditingPageWithInvalidContentsDataPassesExistingDataToPresenter() throws { struct UpdatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "A new title" let contents = "" let tags = ["First Tag", "Second Tag"] @@ -475,8 +475,8 @@ class AdminPostTests: XCTestCase { let testData = try testWorld.createPost() let response = try testWorld.getResponse(to: "/admin/posts/\(testData.post.blogID!)/delete", method: .POST, body: EmptyContent(), loggedInUser: user) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/admin/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/admin/") XCTAssertEqual(testWorld.context.repository.posts.count, 0) } @@ -548,7 +548,7 @@ class AdminPostTests: XCTestCase { let existingTag = try testWorld.createTag(existingTagName) struct UpdatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title = "Post Title" let contents = "# Post Title\n\nWe have a post" let tags: [String] @@ -578,14 +578,14 @@ class AdminPostTests: XCTestCase { let website = "" setenv("WEBSITE_URL", website, 1) let response = try testWorld.getResponse(to: createPostPath, loggedInUser: user) - XCTAssertEqual(response.http.status, .internalServerError) + XCTAssertEqual(response.status, .internalServerError) } // MARK: - Helpers private func createPostViaRequest(title: String) throws -> BlogPost { struct CreatePostData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let title: String let contents = "# Post Title\n\nWe have a post" let tags = ["First Tag", "Second Tag"] diff --git a/Tests/SteamPressTests/AdminTests/AdminUserTests.swift b/Tests/SteamPressTests/AdminTests/AdminUserTests.swift index 04c65127..37043cbd 100644 --- a/Tests/SteamPressTests/AdminTests/AdminUserTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminUserTests.swift @@ -53,7 +53,7 @@ class AdminUserTests: XCTestCase { func testUserCanBeCreatedSuccessfully() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let password = "somepassword" @@ -76,13 +76,13 @@ class AdminUserTests: XCTestCase { XCTAssertEqual(user.tagline, createData.tagline) XCTAssertEqual(user.biography, createData.biography) XCTAssertEqual(user.twitterHandle, createData.twitterHandle) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/admin/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/admin/") } func testUserHasNoAdditionalInfoIfEmptyStringsSent() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let password = "somepassword" @@ -107,7 +107,7 @@ class AdminUserTests: XCTestCase { func testUserMustResetPasswordIfSetToWhenCreatingUser() throws { struct CreateUserResetData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let password = "somepassword" @@ -128,7 +128,7 @@ class AdminUserTests: XCTestCase { func testUserCannotBeCreatedWithoutName() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let username = "lukes" let password = "password" let confirmPassword = "password" @@ -145,7 +145,7 @@ class AdminUserTests: XCTestCase { func testUserCannotBeCreatedWithoutUsername() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let password = "password" let confirmPassword = "password" @@ -162,7 +162,7 @@ class AdminUserTests: XCTestCase { func testUserCannotBeCreatedWithUsernameThatAlreadyExists() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let password = "password" let confirmPassword = "password" @@ -182,7 +182,7 @@ class AdminUserTests: XCTestCase { func testUserCannotBeCreatedWithUsernameThatAlreadyExistsIgnoringCase() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let password = "password" let confirmPassword = "password" @@ -202,7 +202,7 @@ class AdminUserTests: XCTestCase { func testUserCannotBeCreatedWithoutPassword() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let confirmPassword = "password" @@ -219,7 +219,7 @@ class AdminUserTests: XCTestCase { func testUserCannotBeCreatedWithEmptyPassword() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let password = "" @@ -237,7 +237,7 @@ class AdminUserTests: XCTestCase { func testUserCannotBeCreatedWithoutSpecifyingAConfirmPassword() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let password = "password" @@ -254,7 +254,7 @@ class AdminUserTests: XCTestCase { func testUserCannotBeCreatedWithPasswordsThatDontMatch() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let password = "astrongpassword" @@ -275,7 +275,7 @@ class AdminUserTests: XCTestCase { func testUserCannotBeCreatedWithSimplePassword() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let password = "password" @@ -295,7 +295,7 @@ class AdminUserTests: XCTestCase { func testUserCannotBeCreatedWithEmptyName() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let mame = "" let username = "lukes" let password = "password" @@ -313,7 +313,7 @@ class AdminUserTests: XCTestCase { func testUserCannotBeCreatedWithEmptyUsername() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "" let password = "password" @@ -331,7 +331,7 @@ class AdminUserTests: XCTestCase { func testUserCannotBeCreatedWithInvalidUsername() throws { struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes!" let password = "password" @@ -354,7 +354,7 @@ class AdminUserTests: XCTestCase { user = testWorld.createUser(name: "Leia", username: "leia", password: hashedPassword) struct CreateUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let password = "somepassword" @@ -396,7 +396,7 @@ class AdminUserTests: XCTestCase { func testUserCanBeUpdated() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Darth Vader" let username = "darth_vader" } @@ -409,13 +409,13 @@ class AdminUserTests: XCTestCase { XCTAssertEqual(updatedUser.username, editData.username) XCTAssertEqual(updatedUser.name, editData.name) XCTAssertEqual(updatedUser.userID, user.userID) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/admin/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/admin/") } func testUserCanBeUpdatedWithSameUsername() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Leia Organa" let username = "leia" } @@ -428,13 +428,13 @@ class AdminUserTests: XCTestCase { XCTAssertEqual(updatedUser.username, editData.username) XCTAssertEqual(updatedUser.name, editData.name) XCTAssertEqual(updatedUser.userID, user.userID) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/admin/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/admin/") } func testUserCanBeUpdatedWithAllInformation() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Darth Vader" let username = "darth_vader" let twitterHandle = "darthVader" @@ -455,13 +455,13 @@ class AdminUserTests: XCTestCase { XCTAssertEqual(updatedUser.tagline, editData.tagline) XCTAssertEqual(updatedUser.biography, editData.biography) XCTAssertEqual(updatedUser.userID, user.userID) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/admin/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/admin/") } func testOptionalInfoDoesntGetUpdatedWhenEditingUsernameAndSendingEmptyValuesIfSomeAlreadySet() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Darth Vader" let username = "darth_vader" let twitterHandle = "" @@ -491,7 +491,7 @@ class AdminUserTests: XCTestCase { func testUpdatingOptionalInfoToEmptyValuesWhenValueOriginallySetSetsItToNil() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Darth Vader" let username = "darth_vader" let twitterHandle = "" @@ -520,7 +520,7 @@ class AdminUserTests: XCTestCase { func testWhenEditingUserResetPasswordFlagSetIfRequired() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let resetPasswordOnLogin = true @@ -533,13 +533,13 @@ class AdminUserTests: XCTestCase { let updatedUser = try XCTUnwrap(testWorld.context.repository.users.last) XCTAssertTrue(updatedUser.resetPasswordRequired) XCTAssertEqual(updatedUser.userID, user.userID) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/admin/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/admin/") } func testWhenEditingUserResetPasswordFlagNotSetIfSetToFalse() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let resetPasswordOnLogin = false @@ -552,13 +552,13 @@ class AdminUserTests: XCTestCase { let updatedUser = try XCTUnwrap(testWorld.context.repository.users.last) XCTAssertFalse(updatedUser.resetPasswordRequired) XCTAssertEqual(updatedUser.userID, user.userID) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/admin/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/admin/") } func testPasswordIsUpdatedWhenNewPasswordProvidedWhenEditingUser() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let password = "anewpassword" @@ -572,13 +572,13 @@ class AdminUserTests: XCTestCase { let updatedUser = try XCTUnwrap(testWorld.context.repository.users.last) XCTAssertEqual(updatedUser.password, editData.password) XCTAssertEqual(updatedUser.userID, user.userID) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/admin/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/admin/") } func testPasswordIsNotUpdatedWhenEmptyPasswordProvidedWhenEditingUser() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let password = "" @@ -593,13 +593,13 @@ class AdminUserTests: XCTestCase { let updatedUser = try XCTUnwrap(testWorld.context.repository.users.last) XCTAssertEqual(updatedUser.password, oldPassword) XCTAssertEqual(updatedUser.userID, user.userID) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/admin/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/admin/") } func testErrorShownWhenUpdatingUsersPasswordWithNonMatchingPasswords() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let password = "anewpassword" @@ -620,7 +620,7 @@ class AdminUserTests: XCTestCase { func testErrorShownWhenChangingUsersPasswordWithShortPassword() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Luke" let username = "lukes" let password = "a" @@ -643,7 +643,7 @@ class AdminUserTests: XCTestCase { user = testWorld.createUser(name: "Leia", username: "leia", password: hashedPassword) struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Darth Vader" let username = "darth_vader" let password = "somenewpassword" @@ -659,7 +659,7 @@ class AdminUserTests: XCTestCase { func testNameMustBeSetWhenEditingAUser() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "" let username = "darth_vader" let password = "somenewpassword" @@ -677,7 +677,7 @@ class AdminUserTests: XCTestCase { func testUsernameMustBeSetWhenEditingAUser() throws { struct EditUserData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let name = "Darth Vader" let username = "" let password = "somenewpassword" @@ -700,8 +700,8 @@ class AdminUserTests: XCTestCase { let response = try testWorld.getResponse(to: "/admin/users/\(user2.userID!)/delete", body: EmptyContent(), loggedInUser: user) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/admin/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/admin/") XCTAssertEqual(testWorld.context.repository.users.count, 1) XCTAssertNotEqual(testWorld.context.repository.users.last?.name, "Han") } diff --git a/Tests/SteamPressTests/AdminTests/LoginTests.swift b/Tests/SteamPressTests/AdminTests/LoginTests.swift index 47a2ba9e..605d1f61 100644 --- a/Tests/SteamPressTests/AdminTests/LoginTests.swift +++ b/Tests/SteamPressTests/AdminTests/LoginTests.swift @@ -38,40 +38,40 @@ class LoginTests: XCTestCase { let loginData = LoginData(username: user.username, password: "password") let loginResponse = try testWorld.getResponse(to: "/blog/admin/login", method: .POST, body: loginData) - XCTAssertEqual(loginResponse.http.status, .seeOther) - XCTAssertEqual(loginResponse.http.headers[.location].first, "/blog/admin/") - XCTAssertNotNil(loginResponse.http.headers[.setCookie].first) - XCTAssertNotNil(loginResponse.http.cookies["steampress-session"]) + XCTAssertEqual(loginResponse.status, .seeOther) + XCTAssertEqual(loginResponse.headers[.location].first, "/blog/admin/") + XCTAssertNotNil(loginResponse.headers[.setCookie].first) + XCTAssertNotNil(loginResponse.cookies["steampress-session"]) - let sessionCookie = loginResponse.http.cookies["steampress-session"] + let sessionCookie = loginResponse.cookies["steampress-session"] var adminRequest = HTTPRequest(method: .GET, url: URL(string: "/blog/admin")!) adminRequest.cookies["steampress-session"] = sessionCookie let wrappedAdminRequest = Request(http: adminRequest, using: testWorld.context.app!) let adminResponse = try testWorld.getResponse(to: wrappedAdminRequest) - XCTAssertEqual(adminResponse.http.status, .ok) + XCTAssertEqual(adminResponse.status, .ok) var logoutRequest = HTTPRequest(method: .POST, url: URL(string: "/blog/admin/logout")!) logoutRequest.cookies["steampress-session"] = sessionCookie let wrappedLogoutRequest = Request(http: logoutRequest, using: testWorld.context.app!) let logoutResponse = try testWorld.getResponse(to: wrappedLogoutRequest) - XCTAssertEqual(logoutResponse.http.status, .seeOther) - XCTAssertEqual(logoutResponse.http.headers[.location].first, "/blog/") + XCTAssertEqual(logoutResponse.status, .seeOther) + XCTAssertEqual(logoutResponse.headers[.location].first, "/blog/") var secondAdminRequest = HTTPRequest(method: .GET, url: URL(string: "/blog/admin")!) secondAdminRequest.cookies["steampress-session"] = sessionCookie let wrappedSecondRequest = Request(http: secondAdminRequest, using: testWorld.context.app!) let loggedOutAdminResponse = try testWorld.getResponse(to: wrappedSecondRequest) - XCTAssertEqual(loggedOutAdminResponse.http.status, .seeOther) - XCTAssertEqual(loggedOutAdminResponse.http.headers[.location].first, "/blog/admin/login/?loginRequired") + XCTAssertEqual(loggedOutAdminResponse.status, .seeOther) + XCTAssertEqual(loggedOutAdminResponse.headers[.location].first, "/blog/admin/login/?loginRequired") } func testLoginPageCanBeAccessed() throws { let response = try testWorld.getResponse(to: "/blog/admin/login") - XCTAssertEqual(response.http.status, .ok) + XCTAssertEqual(response.status, .ok) } func testLoginWarningShownIfRedirecting() throws { @@ -92,7 +92,7 @@ class LoginTests: XCTestCase { func testUserCanResetPassword() throws { struct ResetPasswordData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let password = "Th3S@m3password" let confirmPassword = "Th3S@m3password" } @@ -101,14 +101,14 @@ class LoginTests: XCTestCase { let response = try testWorld.getResponse(to: "/blog/admin/resetPassword", body: data, loggedInUser: user) XCTAssertEqual(user.password, data.password) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/blog/admin/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/blog/admin/") XCTAssertTrue(testWorld.context.repository.userUpdated) } func testUserCannotResetPasswordWithMismatchingPasswords() throws { struct ResetPasswordData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let password = "Th3S@m3password" let confirmPassword = "An0th3rPass!" } @@ -130,7 +130,7 @@ class LoginTests: XCTestCase { func testUserCannotResetPasswordWithoutPassword() throws { struct ResetPasswordData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let confirmPassword = "Th3S@m3password" } @@ -147,7 +147,7 @@ class LoginTests: XCTestCase { func testUserCannotResetPasswordWithoutConfirmPassword() throws { struct ResetPasswordData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let password = "Th3S@m3password" } @@ -163,7 +163,7 @@ class LoginTests: XCTestCase { func testUserCannotResetPasswordWithShortPassword() throws { struct ResetPasswordData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let password = "apassword" let confirmPassword = "apassword" } @@ -178,7 +178,7 @@ class LoginTests: XCTestCase { func testThatAfterResettingPasswordUserIsNotAskedToResetPassword() throws { let user2 = testWorld.createUser(name: "Han", username: "hans", resetPasswordRequired: true) struct ResetPasswordData: Content { - static let defaultContentType = MediaType.urlEncodedForm + static let defaultContentType = HTTPMediaType.urlEncodedForm let password = "alongpassword" let confirmPassword = "alongpassword" } @@ -188,7 +188,7 @@ class LoginTests: XCTestCase { let response = try testWorld.getResponse(to: "/blog/admin", method: .GET, body: EmptyContent(), loggedInUser: user2) - XCTAssertEqual(response.http.status, .ok) + XCTAssertEqual(response.status, .ok) } func testUserIsRedirectedWhenLoggingInAndPasswordResetRequired() throws { @@ -196,8 +196,8 @@ class LoginTests: XCTestCase { let response = try testWorld.getResponse(to: "/blog/admin/", method: .GET, body: EmptyContent(), loggedInUser: user2) - XCTAssertEqual(response.http.status, .seeOther) - XCTAssertEqual(response.http.headers[.location].first, "/blog/admin/resetPassword/") + XCTAssertEqual(response.status, .seeOther) + XCTAssertEqual(response.headers[.location].first, "/blog/admin/resetPassword/") } func testErrorShownWhenTryingToLoginWithoutUsername() throws { @@ -232,7 +232,7 @@ class LoginTests: XCTestCase { let loginData = LoginData(username: "luke", password: "password", rememberMe: true) let response = try testWorld.getResponse(to: "/blog/admin/login", method: .POST, body: loginData) - let cookieExpiry = try XCTUnwrap(response.http.cookies["steampress-session"]?.expires) + let cookieExpiry = try XCTUnwrap(response.cookies["steampress-session"]?.expires) let oneYear: TimeInterval = 60 * 60 * 24 * 365 XCTAssertEqual(cookieExpiry.timeIntervalSince1970, Date().addingTimeInterval(oneYear).timeIntervalSince1970, accuracy: 1) } @@ -241,7 +241,7 @@ class LoginTests: XCTestCase { let loginData = LoginData(username: "luke", password: "password", rememberMe: nil) let response = try testWorld.getResponse(to: "/blog/admin/login", method: .POST, body: loginData) - let cookie = try XCTUnwrap(response.http.cookies["steampress-session"]) + let cookie = try XCTUnwrap(response.cookies["steampress-session"]) XCTAssertNil(cookie.expires) } @@ -249,7 +249,7 @@ class LoginTests: XCTestCase { let loginData = LoginData(username: "luke", password: "password", rememberMe: false) let response = try testWorld.getResponse(to: "/blog/admin/login", method: .POST, body: loginData) - let cookie = try XCTUnwrap(response.http.cookies["steampress-session"]) + let cookie = try XCTUnwrap(response.cookies["steampress-session"]) XCTAssertNil(cookie.expires) } @@ -260,7 +260,7 @@ class LoginTests: XCTestCase { loginData = LoginData(username: "luke", password: "password", rememberMe: false) let response = try testWorld.getResponse(to: "/blog/admin/login", method: .POST, body: loginData) - let cookie = try XCTUnwrap(response.http.cookies["steampress-session"]) + let cookie = try XCTUnwrap(response.cookies["steampress-session"]) XCTAssertNil(cookie.expires) } @@ -268,13 +268,13 @@ class LoginTests: XCTestCase { let loginData = LoginData(username: "luke", password: "password", rememberMe: true) let loginResponse = try testWorld.getResponse(to: "/blog/admin/login", method: .POST, body: loginData) - let cookie = loginResponse.http.cookies["steampress-session"] + let cookie = loginResponse.cookies["steampress-session"] var adminRequest = HTTPRequest(method: .GET, url: URL(string: "/blog/admin")!) adminRequest.cookies["steampress-session"] = cookie let wrappedAdminRequest = Request(http: adminRequest, using: testWorld.context.app!) let response = try testWorld.getResponse(to: wrappedAdminRequest) - XCTAssertEqual(loginResponse.http.cookies["steampress-session"]?.expires, response.http.cookies["steampress-session"]?.expires) + XCTAssertEqual(loginResponse.cookies["steampress-session"]?.expires, response.cookies["steampress-session"]?.expires) } func testCorrectPageInformationForLogin() throws { diff --git a/Tests/SteamPressTests/BlogTests/AuthorTests.swift b/Tests/SteamPressTests/BlogTests/AuthorTests.swift index b6ae451d..6ffb7093 100644 --- a/Tests/SteamPressTests/BlogTests/AuthorTests.swift +++ b/Tests/SteamPressTests/BlogTests/AuthorTests.swift @@ -59,8 +59,8 @@ class AuthorTests: XCTestCase { let authorResponse = try testWorld.getResponse(to: authorsRequestPath) let allAuthorsResponse = try testWorld.getResponse(to: allAuthorsRequestPath) - XCTAssertEqual(authorResponse.http.status, .notFound) - XCTAssertEqual(allAuthorsResponse.http.status, .notFound) + XCTAssertEqual(authorResponse.status, .notFound) + XCTAssertEqual(allAuthorsResponse.status, .notFound) } func testAuthorView() throws { diff --git a/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift b/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift index e09ac400..09e2b72c 100644 --- a/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift +++ b/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift @@ -8,8 +8,8 @@ class DisabledBlogTagTests: XCTestCase { var tagResponse: Response? = try testWorld.getResponse(to: "/tags/Engineering") var allTagsResponse: Response? = try testWorld.getResponse(to: "/tags") - XCTAssertEqual(.notFound, tagResponse?.http.status) - XCTAssertEqual(.notFound, allTagsResponse?.http.status) + XCTAssertEqual(.notFound, tagResponse?.status) + XCTAssertEqual(.notFound, allTagsResponse?.status) tagResponse = nil allTagsResponse = nil diff --git a/Tests/SteamPressTests/BlogTests/IndexTests.swift b/Tests/SteamPressTests/BlogTests/IndexTests.swift index 1794b8ec..715fdc43 100644 --- a/Tests/SteamPressTests/BlogTests/IndexTests.swift +++ b/Tests/SteamPressTests/BlogTests/IndexTests.swift @@ -71,15 +71,15 @@ class IndexTests: XCTestCase { func testThatAccessingPathsRouteRedirectsToBlogIndex() throws { let response = try testWorld.getResponse(to: "/posts/") - XCTAssertEqual(response.http.status, .movedPermanently) - XCTAssertEqual(response.http.headers[.location].first, "/") + XCTAssertEqual(response.status, .movedPermanently) + XCTAssertEqual(response.headers[.location].first, "/") } func testThatAccessingPathsRouteRedirectsToBlogIndexWithCustomPath() throws { testWorld = try! TestWorld.create(path: "blog") let response = try testWorld.getResponse(to: "/blog/posts/") - XCTAssertEqual(response.http.status, .movedPermanently) - XCTAssertEqual(response.http.headers[.location].first, "/blog/") + XCTAssertEqual(response.status, .movedPermanently) + XCTAssertEqual(response.headers[.location].first, "/blog/") } // MARK: - Pagination Tests diff --git a/Tests/SteamPressTests/BlogTests/SearchTests.swift b/Tests/SteamPressTests/BlogTests/SearchTests.swift index 916d9480..4486058f 100644 --- a/Tests/SteamPressTests/BlogTests/SearchTests.swift +++ b/Tests/SteamPressTests/BlogTests/SearchTests.swift @@ -26,83 +26,83 @@ class SearchTests: XCTestCase { // MARK: - Tests -// func testBlogPassedToSearchPageCorrectly() throws { -// let response = try testWorld.getResponse(to: "/search?term=Test") -// -// XCTAssertEqual(response.http.status, .ok) -// XCTAssertEqual(presenter.searchTerm, "Test") -// XCTAssertEqual(presenter.searchTotalResults, 1) -// XCTAssertEqual(presenter.searchPosts?.first?.title, firstData.post.title) -// } -// -// func testThatSearchTermNilIfEmptySearch() throws { -// let response = try testWorld.getResponse(to: "/search?term=") -// -// XCTAssertEqual(response.http.status, .ok) -// XCTAssertEqual(presenter.searchPosts?.count, 0) -// XCTAssertNil(presenter.searchTerm) -// } -// -// func testThatSearchTermNilIfNoSearchTerm() throws { -// let response = try testWorld.getResponse(to: "/search") -// -// XCTAssertEqual(response.http.status, .ok) -// XCTAssertEqual(presenter.searchPosts?.count, 0) -// XCTAssertNil(presenter.searchTerm) -// } -// -// func testCorrectPageInformationForSearch() throws { -// _ = try testWorld.getResponse(to: "/search?term=Test") -// XCTAssertNil(presenter.searchPageInformation?.disqusName) -// XCTAssertNil(presenter.searchPageInformation?.googleAnalyticsIdentifier) -// XCTAssertNil(presenter.searchPageInformation?.siteTwitterHandle) -// XCTAssertNil(presenter.searchPageInformation?.loggedInUser) -// XCTAssertEqual(presenter.searchPageInformation?.currentPageURL.absoluteString, "/search") -// XCTAssertEqual(presenter.searchPageInformation?.websiteURL.absoluteString, "/") -// } -// -// func testPageInformationGetsLoggedInUserForSearch() throws { -// _ = try testWorld.getResponse(to: "/search?term=Test", loggedInUser: firstData.author) -// XCTAssertEqual(presenter.searchPageInformation?.loggedInUser?.username, firstData.author.username) -// } -// -// func testSettingEnvVarsWithPageInformationForSearch() throws { -// let googleAnalytics = "ABDJIODJWOIJIWO" -// let twitterHandle = "3483209fheihgifffe" -// let disqusName = "34829u48932fgvfbrtewerg" -// setenv("BLOG_GOOGLE_ANALYTICS_IDENTIFIER", googleAnalytics, 1) -// setenv("BLOG_SITE_TWITTER_HANDLE", twitterHandle, 1) -// setenv("BLOG_DISQUS_NAME", disqusName, 1) -// _ = try testWorld.getResponse(to: "/search?term=Test") -// XCTAssertEqual(presenter.searchPageInformation?.disqusName, disqusName) -// XCTAssertEqual(presenter.searchPageInformation?.googleAnalyticsIdentifier, googleAnalytics) -// XCTAssertEqual(presenter.searchPageInformation?.siteTwitterHandle, twitterHandle) -// } -// -// func testPaginationInfoSetCorrectly() throws { -// try testWorld.createPosts(count: 15, author: firstData.author) -// _ = try testWorld.getResponse(to: "/search?term=Test&page=1") -// XCTAssertEqual(presenter.searchPaginationTagInfo?.currentPage, 1) -// XCTAssertEqual(presenter.searchPaginationTagInfo?.totalPages, 1) -// XCTAssertEqual(presenter.searchPaginationTagInfo?.currentQuery, "term=Test&page=1") -// } -// -// func testTagsForSearchPostsSetCorrectly() throws { -// let post2 = try testWorld.createPost(title: "Test Search", author: firstData.author) -// let post3 = try testWorld.createPost(title: "Test Tags", author: firstData.author) -// let tag1Name = "Testing" -// let tag2Name = "Search" -// let tag1 = try testWorld.createTag(tag1Name, on: post2.post) -// _ = try testWorld.createTag(tag2Name, on: firstData.post) -// try testWorld.context.repository.add(tag1, to: firstData.post) -// -// _ = try testWorld.getResponse(to: "/search?term=Test") -// let tagsForPosts = try XCTUnwrap(presenter.searchPageTagsForPost) -// XCTAssertNil(tagsForPosts[post3.post.blogID!]) -// XCTAssertEqual(tagsForPosts[post2.post.blogID!]?.count, 1) -// XCTAssertEqual(tagsForPosts[post2.post.blogID!]?.first?.name, tag1Name) -// XCTAssertEqual(tagsForPosts[firstData.post.blogID!]?.count, 2) -// XCTAssertEqual(tagsForPosts[firstData.post.blogID!]?.first?.name, tag1Name) -// XCTAssertEqual(tagsForPosts[firstData.post.blogID!]?.last?.name, tag2Name) -// } + func testBlogPassedToSearchPageCorrectly() throws { + let response = try testWorld.getResponse(to: "/search?term=Test") + + XCTAssertEqual(response.status, .ok) + XCTAssertEqual(presenter.searchTerm, "Test") + XCTAssertEqual(presenter.searchTotalResults, 1) + XCTAssertEqual(presenter.searchPosts?.first?.title, firstData.post.title) + } + + func testThatSearchTermNilIfEmptySearch() throws { + let response = try testWorld.getResponse(to: "/search?term=") + + XCTAssertEqual(response.status, .ok) + XCTAssertEqual(presenter.searchPosts?.count, 0) + XCTAssertNil(presenter.searchTerm) + } + + func testThatSearchTermNilIfNoSearchTerm() throws { + let response = try testWorld.getResponse(to: "/search") + + XCTAssertEqual(response.status, .ok) + XCTAssertEqual(presenter.searchPosts?.count, 0) + XCTAssertNil(presenter.searchTerm) + } + + func testCorrectPageInformationForSearch() throws { + _ = try testWorld.getResponse(to: "/search?term=Test") + XCTAssertNil(presenter.searchPageInformation?.disqusName) + XCTAssertNil(presenter.searchPageInformation?.googleAnalyticsIdentifier) + XCTAssertNil(presenter.searchPageInformation?.siteTwitterHandle) + XCTAssertNil(presenter.searchPageInformation?.loggedInUser) + XCTAssertEqual(presenter.searchPageInformation?.currentPageURL.absoluteString, "/search") + XCTAssertEqual(presenter.searchPageInformation?.websiteURL.absoluteString, "/") + } + + func testPageInformationGetsLoggedInUserForSearch() throws { + _ = try testWorld.getResponse(to: "/search?term=Test", loggedInUser: firstData.author) + XCTAssertEqual(presenter.searchPageInformation?.loggedInUser?.username, firstData.author.username) + } + + func testSettingEnvVarsWithPageInformationForSearch() throws { + let googleAnalytics = "ABDJIODJWOIJIWO" + let twitterHandle = "3483209fheihgifffe" + let disqusName = "34829u48932fgvfbrtewerg" + setenv("BLOG_GOOGLE_ANALYTICS_IDENTIFIER", googleAnalytics, 1) + setenv("BLOG_SITE_TWITTER_HANDLE", twitterHandle, 1) + setenv("BLOG_DISQUS_NAME", disqusName, 1) + _ = try testWorld.getResponse(to: "/search?term=Test") + XCTAssertEqual(presenter.searchPageInformation?.disqusName, disqusName) + XCTAssertEqual(presenter.searchPageInformation?.googleAnalyticsIdentifier, googleAnalytics) + XCTAssertEqual(presenter.searchPageInformation?.siteTwitterHandle, twitterHandle) + } + + func testPaginationInfoSetCorrectly() throws { + try testWorld.createPosts(count: 15, author: firstData.author) + _ = try testWorld.getResponse(to: "/search?term=Test&page=1") + XCTAssertEqual(presenter.searchPaginationTagInfo?.currentPage, 1) + XCTAssertEqual(presenter.searchPaginationTagInfo?.totalPages, 1) + XCTAssertEqual(presenter.searchPaginationTagInfo?.currentQuery, "term=Test&page=1") + } + + func testTagsForSearchPostsSetCorrectly() throws { + let post2 = try testWorld.createPost(title: "Test Search", author: firstData.author) + let post3 = try testWorld.createPost(title: "Test Tags", author: firstData.author) + let tag1Name = "Testing" + let tag2Name = "Search" + let tag1 = try testWorld.createTag(tag1Name, on: post2.post) + _ = try testWorld.createTag(tag2Name, on: firstData.post) + try testWorld.context.repository.add(tag1, to: firstData.post) + + _ = try testWorld.getResponse(to: "/search?term=Test") + let tagsForPosts = try XCTUnwrap(presenter.searchPageTagsForPost) + XCTAssertNil(tagsForPosts[post3.post.blogID!]) + XCTAssertEqual(tagsForPosts[post2.post.blogID!]?.count, 1) + XCTAssertEqual(tagsForPosts[post2.post.blogID!]?.first?.name, tag1Name) + XCTAssertEqual(tagsForPosts[firstData.post.blogID!]?.count, 2) + XCTAssertEqual(tagsForPosts[firstData.post.blogID!]?.first?.name, tag1Name) + XCTAssertEqual(tagsForPosts[firstData.post.blogID!]?.last?.name, tag2Name) + } } diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Responses.swift b/Tests/SteamPressTests/Helpers/TestWorld+Responses.swift index 4cb3deab..29c0ed46 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Responses.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Responses.swift @@ -8,7 +8,7 @@ extension TestWorld { } func getResponseString(to path: String, headers: HTTPHeaders = .init()) throws -> String { - let data = try getResponse(to: path, headers: headers).http.body.convertToHTTPBody().data + let data = try getResponse(to: path, headers: headers).body.convertToHTTPBody().data return String(data: data!, encoding: .utf8)! } @@ -41,7 +41,7 @@ extension TestWorld { loginPath = "/\(path)\(loginPath)" } let loginResponse = try getResponse(to: loginPath, method: .POST, body: loginData) - let sessionCookie = loginResponse.http.cookies["steampress-session"] + let sessionCookie = loginResponse.cookies["steampress-session"] return sessionCookie } else { return nil From b266b40b3bd45ec6844b4ad9a580d33e6eef6091 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 3 Apr 2020 17:03:06 +0100 Subject: [PATCH 29/70] Most services compiling --- .../Repositories/SteamPressRepository.swift | 1 - .../Presenters/CapturingAdminPresenter.swift | 35 ++--- .../Presenters/CapturingBlogPresenter.swift | 41 +++--- .../Helpers/InMemoryRepository.swift | 130 +++++++++--------- .../Helpers/TestDataBuilder.swift | 12 +- 5 files changed, 116 insertions(+), 103 deletions(-) diff --git a/Sources/SteamPress/Repositories/SteamPressRepository.swift b/Sources/SteamPress/Repositories/SteamPressRepository.swift index d2df40d5..df88f35d 100644 --- a/Sources/SteamPress/Repositories/SteamPressRepository.swift +++ b/Sources/SteamPress/Repositories/SteamPressRepository.swift @@ -36,7 +36,6 @@ public protocol BlogPostRepository: SteamPressRepository { } public protocol BlogUserRepository: SteamPressRepository { - init(application: Application) func getAllUsers() -> EventLoopFuture<[BlogUser]> func getAllUsersWithPostCount() -> EventLoopFuture<[(BlogUser, Int)]> func getUser(id: Int) -> EventLoopFuture diff --git a/Tests/SteamPressTests/Fakes/Presenters/CapturingAdminPresenter.swift b/Tests/SteamPressTests/Fakes/Presenters/CapturingAdminPresenter.swift index 2abbb3dd..7164601c 100644 --- a/Tests/SteamPressTests/Fakes/Presenters/CapturingAdminPresenter.swift +++ b/Tests/SteamPressTests/Fakes/Presenters/CapturingAdminPresenter.swift @@ -1,19 +1,28 @@ -import SteamPress +@testable import SteamPress import Vapor class CapturingAdminPresenter: BlogAdminPresenter { + + let eventLoop: EventLoop + init(eventLoop: EventLoop) { + self.eventLoop = eventLoop + } + + func `for`(_ request: Request, pathCreator: BlogPathCreator) -> BlogAdminPresenter { + return CapturingAdminPresenter(eventLoop: request.eventLoop) + } // MARK: - BlogPresenter private(set) var adminViewErrors: [String]? private(set) var adminViewPosts: [BlogPost]? private(set) var adminViewUsers: [BlogUser]? private(set) var adminViewPageInformation: BlogAdminPageInformation? - func createIndexView(on container: Container, posts: [BlogPost], users: [BlogUser], errors: [String]?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { + func createIndexView(posts: [BlogPost], users: [BlogUser], errors: [String]?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { self.adminViewErrors = errors self.adminViewPosts = posts self.adminViewUsers = users self.adminViewPageInformation = pageInformation - return createFutureView(on: container) + return TestDataBuilder.createFutureView(on: eventLoop) } private(set) var createPostErrors: [String]? @@ -27,7 +36,7 @@ class CapturingAdminPresenter: BlogAdminPresenter { private(set) var createPostTitleError: Bool? private(set) var createPostContentsError: Bool? private(set) var createPostPageInformation: BlogAdminPageInformation? - func createPostView(on container: Container, errors: [String]?, title: String?, contents: String?, slugURL: String?, tags: [String]?, isEditing: Bool, post: BlogPost?, isDraft: Bool?, titleError: Bool, contentsError: Bool, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { + func createPostView(errors: [String]?, title: String?, contents: String?, slugURL: String?, tags: [String]?, isEditing: Bool, post: BlogPost?, isDraft: Bool?, titleError: Bool, contentsError: Bool, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { self.createPostErrors = errors self.createPostTitle = title self.createPostContents = contents @@ -39,7 +48,7 @@ class CapturingAdminPresenter: BlogAdminPresenter { self.createPostTitleError = titleError self.createPostContentsError = contentsError self.createPostPageInformation = pageInformation - return createFutureView(on: container) + return TestDataBuilder.createFutureView(on: eventLoop) } private(set) var createUserErrors: [String]? @@ -56,7 +65,7 @@ class CapturingAdminPresenter: BlogAdminPresenter { private(set) var createUserEditing: Bool? private(set) var createUserNameError: Bool? private(set) var createUserUsernameError: Bool? - func createUserView(on container: Container, editing: Bool, errors: [String]?, name: String?, nameError: Bool, username: String?, usernameErorr: Bool, passwordError: Bool, confirmPasswordError: Bool, resetPasswordOnLogin: Bool, userID: Int?, profilePicture: String?, twitterHandle: String?, biography: String?, tagline: String?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { + func createUserView(editing: Bool, errors: [String]?, name: String?, nameError: Bool, username: String?, usernameErorr: Bool, passwordError: Bool, confirmPasswordError: Bool, resetPasswordOnLogin: Bool, userID: Int?, profilePicture: String?, twitterHandle: String?, biography: String?, tagline: String?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { self.createUserEditing = editing self.createUserErrors = errors self.createUserName = name @@ -71,26 +80,18 @@ class CapturingAdminPresenter: BlogAdminPresenter { self.createUserNameError = nameError self.createUserUsernameError = usernameErorr self.createUserResetPasswordRequired = resetPasswordOnLogin - return createFutureView(on: container) + return TestDataBuilder.createFutureView(on: eventLoop) } private(set) var resetPasswordErrors: [String]? private(set) var resetPasswordError: Bool? private(set) var resetPasswordConfirmError: Bool? private(set) var resetPasswordPageInformation: BlogAdminPageInformation? - func createResetPasswordView(on container: Container, errors: [String]?, passwordError: Bool?, confirmPasswordError: Bool?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { + func createResetPasswordView(errors: [String]?, passwordError: Bool?, confirmPasswordError: Bool?, pageInformation: BlogAdminPageInformation) -> EventLoopFuture { self.resetPasswordErrors = errors self.resetPasswordError = passwordError self.resetPasswordConfirmError = confirmPasswordError self.resetPasswordPageInformation = pageInformation - return createFutureView(on: container) - } - - // MARK: - Helpers - - func createFutureView(on container: Container) -> EventLoopFuture { - let data = "some HTML".convertToData() - let view = View(data: data) - return container.future(view) + return TestDataBuilder.createFutureView(on: eventLoop) } } diff --git a/Tests/SteamPressTests/Fakes/Presenters/CapturingBlogPresenter.swift b/Tests/SteamPressTests/Fakes/Presenters/CapturingBlogPresenter.swift index 5a88da34..d1242cdd 100644 --- a/Tests/SteamPressTests/Fakes/Presenters/CapturingBlogPresenter.swift +++ b/Tests/SteamPressTests/Fakes/Presenters/CapturingBlogPresenter.swift @@ -4,6 +4,15 @@ import Vapor import Foundation class CapturingBlogPresenter: BlogPresenter { + + let eventLoop: EventLoop + init(eventLoop: EventLoop) { + self.eventLoop = eventLoop + } + + func `for`(_ request: Request) -> BlogPresenter { + return CapturingBlogPresenter(eventLoop: request.eventLoop) + } // MARK: - BlogPresenter private(set) var indexPosts: [BlogPost]? @@ -12,36 +21,36 @@ class CapturingBlogPresenter: BlogPresenter { private(set) var indexPageInformation: BlogGlobalPageInformation? private(set) var indexPaginationTagInfo: PaginationTagInformation? private(set) var indexTagsForPosts: [Int: [BlogTag]]? - func indexView(on container: Container, posts: [BlogPost], tags: [BlogTag], authors: [BlogUser], tagsForPosts: [Int : [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { + func indexView(posts: [BlogPost], tags: [BlogTag], authors: [BlogUser], tagsForPosts: [Int : [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { self.indexPosts = posts self.indexTags = tags self.indexAuthors = authors self.indexPageInformation = pageInformation self.indexPaginationTagInfo = paginationTagInfo self.indexTagsForPosts = tagsForPosts - return TestDataBuilder.createFutureView(on: container) + return TestDataBuilder.createFutureView(on: eventLoop) } private(set) var post: BlogPost? private(set) var postAuthor: BlogUser? private(set) var postPageInformation: BlogGlobalPageInformation? private(set) var postPageTags: [BlogTag]? - func postView(on container: Container, post: BlogPost, author: BlogUser, tags: [BlogTag], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { + func postView(post: BlogPost, author: BlogUser, tags: [BlogTag], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { self.post = post self.postAuthor = author self.postPageInformation = pageInformation self.postPageTags = tags - return TestDataBuilder.createFutureView(on: container) + return TestDataBuilder.createFutureView(on: eventLoop) } private(set) var allAuthors: [BlogUser]? private(set) var allAuthorsPostCount: [Int: Int]? private(set) var allAuthorsPageInformation: BlogGlobalPageInformation? - func allAuthorsView(on container: Container, authors: [BlogUser], authorPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { + func allAuthorsView(authors: [BlogUser], authorPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { self.allAuthors = authors self.allAuthorsPostCount = authorPostCounts self.allAuthorsPageInformation = pageInformation - return TestDataBuilder.createFutureView(on: container) + return TestDataBuilder.createFutureView(on: eventLoop) } private(set) var author: BlogUser? @@ -50,24 +59,24 @@ class CapturingBlogPresenter: BlogPresenter { private(set) var authorPageInformation: BlogGlobalPageInformation? private(set) var authorPaginationTagInfo: PaginationTagInformation? private(set) var authorPageTagsForPost: [Int: [BlogTag]]? - func authorView(on container: Container, author: BlogUser, posts: [BlogPost], postCount: Int, tagsForPosts: [Int : [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { + func authorView(author: BlogUser, posts: [BlogPost], postCount: Int, tagsForPosts: [Int : [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { self.author = author self.authorPosts = posts self.authorPostCount = postCount self.authorPageInformation = pageInformation self.authorPaginationTagInfo = paginationTagInfo self.authorPageTagsForPost = tagsForPosts - return TestDataBuilder.createFutureView(on: container) + return TestDataBuilder.createFutureView(on: eventLoop) } private(set) var allTagsPageTags: [BlogTag]? private(set) var allTagsPagePostCount: [Int: Int]? private(set) var allTagsPageInformation: BlogGlobalPageInformation? - func allTagsView(on container: Container, tags: [BlogTag], tagPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { + func allTagsView(tags: [BlogTag], tagPostCounts: [Int: Int], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { self.allTagsPageTags = tags self.allTagsPagePostCount = tagPostCounts self.allTagsPageInformation = pageInformation - return TestDataBuilder.createFutureView(on: container) + return TestDataBuilder.createFutureView(on: eventLoop) } private(set) var tag: BlogTag? @@ -76,14 +85,14 @@ class CapturingBlogPresenter: BlogPresenter { private(set) var tagPaginationTagInfo: PaginationTagInformation? private(set) var tagPageTotalPosts: Int? private(set) var tagPageAuthors: [BlogUser]? - func tagView(on container: Container, tag: BlogTag, posts: [BlogPost], authors: [BlogUser], totalPosts: Int, pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { + func tagView(tag: BlogTag, posts: [BlogPost], authors: [BlogUser], totalPosts: Int, pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { self.tag = tag self.tagPosts = posts self.tagPageInformation = pageInformation self.tagPaginationTagInfo = paginationTagInfo self.tagPageTotalPosts = totalPosts self.tagPageAuthors = authors - return TestDataBuilder.createFutureView(on: container) + return TestDataBuilder.createFutureView(on: eventLoop) } private(set) var searchPosts: [BlogPost]? @@ -93,7 +102,7 @@ class CapturingBlogPresenter: BlogPresenter { private(set) var searchPageInformation: BlogGlobalPageInformation? private(set) var searchPaginationTagInfo: PaginationTagInformation? private(set) var searchPageTagsForPost: [Int: [BlogTag]]? - func searchView(on container: Container, totalResults: Int, posts: [BlogPost], authors: [BlogUser], searchTerm: String?, tagsForPosts: [Int : [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { + func searchView(totalResults: Int, posts: [BlogPost], authors: [BlogUser], searchTerm: String?, tagsForPosts: [Int : [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture { self.searchPosts = posts self.searchTerm = searchTerm self.searchPageInformation = pageInformation @@ -101,7 +110,7 @@ class CapturingBlogPresenter: BlogPresenter { self.searchAuthors = authors self.searchPaginationTagInfo = paginationTagInfo self.searchPageTagsForPost = tagsForPosts - return TestDataBuilder.createFutureView(on: container) + return TestDataBuilder.createFutureView(on: eventLoop) } private(set) var loginWarning: Bool? @@ -111,7 +120,7 @@ class CapturingBlogPresenter: BlogPresenter { private(set) var loginPasswordError: Bool? private(set) var loginPageInformation: BlogGlobalPageInformation? private(set) var loginPageRememberMe: Bool? - func loginView(on container: Container, loginWarning: Bool, errors: [String]?, username: String?, usernameError: Bool, passwordError: Bool, rememberMe: Bool, pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { + func loginView(loginWarning: Bool, errors: [String]?, username: String?, usernameError: Bool, passwordError: Bool, rememberMe: Bool, pageInformation: BlogGlobalPageInformation) -> EventLoopFuture { self.loginWarning = loginWarning self.loginErrors = errors self.loginUsername = username @@ -119,6 +128,6 @@ class CapturingBlogPresenter: BlogPresenter { self.loginPasswordError = passwordError self.loginPageInformation = pageInformation self.loginPageRememberMe = rememberMe - return TestDataBuilder.createFutureView(on: container) + return TestDataBuilder.createFutureView(on: eventLoop) } } diff --git a/Tests/SteamPressTests/Helpers/InMemoryRepository.swift b/Tests/SteamPressTests/Helpers/InMemoryRepository.swift index c4f71154..b7b2eeab 100644 --- a/Tests/SteamPressTests/Helpers/InMemoryRepository.swift +++ b/Tests/SteamPressTests/Helpers/InMemoryRepository.swift @@ -1,35 +1,37 @@ import Vapor import SteamPress -class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserRepository, Service { +class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserRepository { private(set) var tags: [BlogTag] private(set) var posts: [BlogPost] private(set) var users: [BlogUser] private(set) var postTagLinks: [BlogPostTagLink] + private(set) var eventLoop: EventLoop - init() { + init(eventLoop: EventLoop) { tags = [] posts = [] users = [] postTagLinks = [] + self.eventLoop = eventLoop } // MARK: - BlogTagRepository - func getAllTags(on container: Container) -> EventLoopFuture<[BlogTag]> { - return container.future(tags) + func getAllTags() -> EventLoopFuture<[BlogTag]> { + return eventLoop.future(tags) } - func getAllTagsWithPostCount(on container: Container) -> EventLoopFuture<[(BlogTag, Int)]> { + func getAllTagsWithPostCount() -> EventLoopFuture<[(BlogTag, Int)]> { let tagsWithCount = tags.map { tag -> (BlogTag, Int) in let postCount = postTagLinks.filter { $0.tagID == tag.tagID }.count return (tag, postCount) } - return container.future(tagsWithCount) + return eventLoop.future(tagsWithCount) } - func getTagsForAllPosts(on container: Container) -> EventLoopFuture<[Int : [BlogTag]]> { + func getTagsForAllPosts() -> EventLoopFuture<[Int : [BlogTag]]> { var dict = [Int: [BlogTag]]() for tag in tags { postTagLinks.filter { $0.tagID == tag.tagID }.forEach { link in @@ -41,10 +43,10 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit } } } - return container.future(dict) + return eventLoop.future(dict) } - func getTags(for post: BlogPost, on container: Container) -> EventLoopFuture<[BlogTag]> { + func getTags(for post: BlogPost) -> EventLoopFuture<[BlogTag]> { var results = [BlogTag]() guard let postID = post.blogID else { fatalError("Post doesn't exist when it should") @@ -56,15 +58,15 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit } results.append(tag) } - return container.future(results) + return eventLoop.future(results) } - func save(_ tag: BlogTag, on container: Container) -> EventLoopFuture { + func save(_ tag: BlogTag) -> EventLoopFuture { if tag.tagID == nil { tag.tagID = tags.count + 1 } tags.append(tag) - return container.future(tag) + return eventLoop.future(tag) } func addTag(name: String) throws -> BlogTag { @@ -73,16 +75,16 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit return newTag } - func add(_ tag: BlogTag, to post: BlogPost, on container: Container) -> EventLoopFuture { + func add(_ tag: BlogTag, to post: BlogPost) -> EventLoopFuture { do { - try add(tag, to: post) - return container.future() + try internalAdd(tag, to: post) + return eventLoop.future() } catch { - return container.future(error: SteamPressTestError(name: "Failed to add tag to post")) + return eventLoop.future(error: SteamPressTestError(name: "Failed to add tag to post")) } } - func add(_ tag: BlogTag, to post: BlogPost) throws { + func internalAdd(_ tag: BlogTag, to post: BlogPost) throws { guard let postID = post.blogID else { fatalError("Blog doesn't exist when it should") } @@ -95,12 +97,12 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit func addTag(name: String, for post: BlogPost) throws -> BlogTag { let newTag = try addTag(name: name) - try add(newTag, to: post) + try internalAdd(newTag, to: post) return newTag } - func getTag(_ name: String, on container: Container) -> EventLoopFuture { - return container.future(tags.first { $0.name == name }) + func getTag(_ name: String) -> EventLoopFuture { + return eventLoop.future(tags.first { $0.name == name }) } func addTag(_ tag: BlogTag, to post: BlogPost) { @@ -114,48 +116,48 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit postTagLinks.append(newLink) } - func deleteTags(for post: BlogPost, on container: Container) -> EventLoopFuture { - return getTags(for: post, on: container).map { tags in + func deleteTags(for post: BlogPost) -> EventLoopFuture { + return getTags(for: post).map { tags in for tag in tags { self.postTagLinks.removeAll { $0.tagID == tag.tagID! && $0.postID == post.blogID! } } } } - func remove(_ tag: BlogTag, from post: BlogPost, on container: Container) -> EventLoopFuture { + func remove(_ tag: BlogTag, from post: BlogPost) -> EventLoopFuture { self.postTagLinks.removeAll { $0.tagID == tag.tagID! && $0.postID == post.blogID! } - return container.future() + return eventLoop.future() } // MARK: - BlogPostRepository - func getAllPostsSortedByPublishDate(includeDrafts: Bool, on container: Container) -> EventLoopFuture<[BlogPost]> { + func getAllPostsSortedByPublishDate(includeDrafts: Bool) -> EventLoopFuture<[BlogPost]> { var sortedPosts = posts.sorted { $0.created > $1.created } if !includeDrafts { sortedPosts = sortedPosts.filter { $0.published } } - return container.future(sortedPosts) + return eventLoop.future(sortedPosts) } - func getAllPostsSortedByPublishDate(includeDrafts: Bool, on container: Container, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> { + func getAllPostsSortedByPublishDate(includeDrafts: Bool, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> { var sortedPosts = posts.sorted { $0.created > $1.created } if !includeDrafts { sortedPosts = sortedPosts.filter { $0.published } } let startIndex = min(offset, sortedPosts.count) let endIndex = min(offset + count, sortedPosts.count) - return container.future(Array(sortedPosts[startIndex.. EventLoopFuture { + func getAllPostsCount(includeDrafts: Bool) -> EventLoopFuture { var sortedPosts = posts.sorted { $0.created > $1.created } if !includeDrafts { sortedPosts = sortedPosts.filter { $0.published } } - return container.future(sortedPosts.count) + return eventLoop.future(sortedPosts.count) } - func getAllPostsSortedByPublishDate(for user: BlogUser, includeDrafts: Bool, on container: Container, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> { + func getAllPostsSortedByPublishDate(for user: BlogUser, includeDrafts: Bool, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> { let authorsPosts = posts.filter { $0.author == user.userID } var sortedPosts = authorsPosts.sorted { $0.created > $1.created } if !includeDrafts { @@ -163,22 +165,22 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit } let startIndex = min(offset, sortedPosts.count) let endIndex = min(offset + count, sortedPosts.count) - return container.future(Array(sortedPosts[startIndex.. EventLoopFuture { - return container.future(posts.filter { $0.author == user.userID }.count) + func getPostCount(for user: BlogUser) -> EventLoopFuture { + return eventLoop.future(posts.filter { $0.author == user.userID }.count) } - func getPost(slug: String, on container: Container) -> EventLoopFuture { - return container.future(posts.first { $0.slugUrl == slug }) + func getPost(slug: String) -> EventLoopFuture { + return eventLoop.future(posts.first { $0.slugUrl == slug }) } - func getPost(id: Int, on container: Container) -> EventLoopFuture { - return container.future(posts.first { $0.blogID == id }) + func getPost(id: Int) -> EventLoopFuture { + return eventLoop.future(posts.first { $0.blogID == id }) } - func getSortedPublishedPosts(for tag: BlogTag, on container: Container, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> { + func getSortedPublishedPosts(for tag: BlogTag, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> { var results = [BlogPost]() guard let tagID = tag.tagID else { fatalError("Tag doesn't exist when it should") @@ -193,10 +195,10 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit let sortedPosts = results.sorted { $0.created > $1.created }.filter { $0.published } let startIndex = min(offset, sortedPosts.count) let endIndex = min(offset + count, sortedPosts.count) - return container.future(Array(sortedPosts[startIndex.. EventLoopFuture { + func getPublishedPostCount(for tag: BlogTag) -> EventLoopFuture { var results = [BlogPost]() guard let tagID = tag.tagID else { fatalError("Tag doesn't exist when it should") @@ -209,26 +211,26 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit results.append(post) } let sortedPosts = results.sorted { $0.created > $1.created }.filter { $0.published } - return container.future(sortedPosts.count) + return eventLoop.future(sortedPosts.count) } - func getPublishedPostCount(for searchTerm: String, on container: Container) -> EventLoopFuture { + func getPublishedPostCount(for searchTerm: String) -> EventLoopFuture { let titleResults = posts.filter { $0.title.contains(searchTerm) } let results = titleResults.sorted { $0.created > $1.created }.filter { $0.published } - return container.future(results.count) + return eventLoop.future(results.count) } - func findPublishedPostsOrdered(for searchTerm: String, on container: Container, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> { + func findPublishedPostsOrdered(for searchTerm: String, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> { let titleResults = posts.filter { $0.title.contains(searchTerm) } let results = titleResults.sorted { $0.created > $1.created }.filter { $0.published } let startIndex = min(offset, results.count) let endIndex = min(offset + count, results.count) - return container.future(Array(results[startIndex.. EventLoopFuture { + func save(_ post: BlogPost) -> EventLoopFuture { self.add(post) - return container.future(post) + return eventLoop.future(post) } func add(_ post: BlogPost) { @@ -238,9 +240,9 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit } } - func delete(_ post: BlogPost, on container: Container) -> EventLoopFuture { + func delete(_ post: BlogPost) -> EventLoopFuture { posts.removeAll { $0.blogID == post.blogID } - return container.future() + return eventLoop.future() } // MARK: - BlogUserRepository @@ -255,40 +257,40 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit } } - func getUser(id: Int, on container: Container) -> EventLoopFuture { - return container.future(users.first { $0.userID == id }) + func getUser(id: Int) -> EventLoopFuture { + return eventLoop.future(users.first { $0.userID == id }) } - func getAllUsers(on container: Container) -> EventLoopFuture<[BlogUser]> { - return container.future(users) + func getAllUsers() -> EventLoopFuture<[BlogUser]> { + return eventLoop.future(users) } - func getAllUsersWithPostCount(on container: Container) -> EventLoopFuture<[(BlogUser, Int)]> { + func getAllUsersWithPostCount() -> EventLoopFuture<[(BlogUser, Int)]> { let usersWithCount = users.map { user -> (BlogUser, Int) in let postCount = posts.filter { $0.author == user.userID }.count return (user, postCount) } - return container.future(usersWithCount) + return eventLoop.future(usersWithCount) } - func getUser(username: String, on container: Container) -> EventLoopFuture { - return container.future(users.first { $0.username == username }) + func getUser(username: String) -> EventLoopFuture { + return eventLoop.future(users.first { $0.username == username }) } private(set) var userUpdated = false - func save(_ user: BlogUser, on container: Container) -> EventLoopFuture { + func save(_ user: BlogUser) -> EventLoopFuture { self.add(user) userUpdated = true - return container.future(user) + return eventLoop.future(user) } - func delete(_ user: BlogUser, on container: Container) -> EventLoopFuture { + func delete(_ user: BlogUser) -> EventLoopFuture { users.removeAll { $0.userID == user.userID } - return container.future() + return eventLoop.future() } - func getUsersCount(on container: Container) -> EventLoopFuture { - return container.future(users.count) + func getUsersCount() -> EventLoopFuture { + return eventLoop.future(users.count) } } diff --git a/Tests/SteamPressTests/Helpers/TestDataBuilder.swift b/Tests/SteamPressTests/Helpers/TestDataBuilder.swift index 00ca976e..1dba196b 100644 --- a/Tests/SteamPressTests/Helpers/TestDataBuilder.swift +++ b/Tests/SteamPressTests/Helpers/TestDataBuilder.swift @@ -50,11 +50,13 @@ struct TestDataBuilder { repository.add(user) return user } - - static func createFutureView(on container: Container) -> EventLoopFuture { - let data = "some HTML".convertToData() - let view = View(data: data) - return container.future(view) + + static func createFutureView(on eventLoop: EventLoop) -> EventLoopFuture { + let string = "Some HTML" + var byteBuffer = ByteBufferAllocator().buffer(capacity: string.count) + byteBuffer.writeString("Some HTML") + let view = View(data: byteBuffer) + return eventLoop.future(view) } } From 53f1df30e0c5cfcedbd9f0e789a01578a8f8370a Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 3 Apr 2020 17:06:43 +0100 Subject: [PATCH 30/70] Bring back the CapturingViewRenderer --- .../Fakes/CapturingViewRenderer.swift | 38 ++++++++++--------- .../ViewTests/BlogPresenterTests.swift | 2 +- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift b/Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift index 618dc504..34ab230f 100644 --- a/Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift +++ b/Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift @@ -1,19 +1,23 @@ import Vapor -//class CapturingViewRenderer: ViewRenderer, Service { -// var shouldCache = false -// var worker: Worker -// -// init(worker: Worker) { -// self.worker = worker -// } -// -// private(set) var capturedContext: Encodable? -// private(set) var templatePath: String? -// func render(_ path: String, _ context: E, userInfo: [AnyHashable: Any]) -> EventLoopFuture where E: Encodable { -// self.capturedContext = context -// self.templatePath = path -// return Future.map(on: worker) { return View(data: "Test".convertToData()) } -// } -// -//} +class CapturingViewRenderer: ViewRenderer { + var shouldCache = false + var eventLoop: EventLoop + + init(eventLoop: EventLoop) { + self.eventLoop = eventLoop + } + + func `for`(_ request: Request) -> ViewRenderer { + return CapturingViewRenderer(eventLoop: request.eventLoop) + } + + private(set) var capturedContext: Encodable? + private(set) var templatePath: String? + func render(_ name: String, _ context: E) -> EventLoopFuture where E : Encodable { + self.capturedContext = context + self.templatePath = name + return TestDataBuilder.createFutureView(on: eventLoop) + } + +} diff --git a/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift b/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift index 1e0b3a9b..22a3613e 100644 --- a/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift @@ -5,7 +5,7 @@ import Vapor class BlogPresenterTests: XCTestCase { // MARK: - Properties - var basicContainer: BasicContainer! +// var basicContainer: BasicContainer! var presenter: ViewBlogPresenter! var viewRenderer: CapturingViewRenderer! var testTag: BlogTag! From e8d75afbc5c9ee26625df252dac132e230db13cb Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 3 Apr 2020 17:11:11 +0100 Subject: [PATCH 31/70] Get BlogPresenterTests compiling --- .../ViewTests/BlogPresenterTests.swift | 87 +++++++++---------- 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift b/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift index 22a3613e..d5c1cf0c 100644 --- a/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift @@ -5,7 +5,7 @@ import Vapor class BlogPresenterTests: XCTestCase { // MARK: - Properties -// var basicContainer: BasicContainer! + var eventLoopGroup: MultiThreadedEventLoopGroup! var presenter: ViewBlogPresenter! var viewRenderer: CapturingViewRenderer! var testTag: BlogTag! @@ -26,19 +26,14 @@ class BlogPresenterTests: XCTestCase { // MARK: - Overrides override func setUp() { - presenter = ViewBlogPresenter() - basicContainer = BasicContainer(config: Config.default(), environment: Environment.testing, services: .init(), on: EmbeddedEventLoop()) - basicContainer.services.register(ViewRenderer.self) { _ in - return self.viewRenderer - } - basicContainer.services.register(LongPostDateFormatter.self) - basicContainer.services.register(NumericPostDateFormatter.self) - viewRenderer = CapturingViewRenderer(worker: basicContainer) + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + let viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) + presenter = ViewBlogPresenter(viewRenderer: viewRenderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: eventLoopGroup) testTag = BlogTag(id: 1, name: "Tattoine") } override func tearDown() { - try! basicContainer.syncShutdownGracefully() + try! eventLoopGroup.syncShutdownGracefully() } // MARK: - Tests @@ -49,7 +44,7 @@ class BlogPresenterTests: XCTestCase { let tags = [BlogTag(id: 0, name: "tag1"), BlogTag(id: 1, name: "tag2")] let pageInformation = buildPageInformation(currentPageURL: allTagsURL) - _ = presenter.allTagsView(on: basicContainer, tags: tags, tagPostCounts: [:], pageInformation: pageInformation) + _ = presenter.allTagsView(tags: tags, tagPostCounts: [:], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllTagsPageContext) @@ -71,7 +66,7 @@ class BlogPresenterTests: XCTestCase { let tags = [tag1, tag2] let tagPostCount = [0: 5, 1: 20] let pageInformation = buildPageInformation(currentPageURL: allTagsURL) - _ = presenter.allTagsView(on: basicContainer, tags: tags, tagPostCounts: tagPostCount, pageInformation: pageInformation) + _ = presenter.allTagsView(tags: tags, tagPostCounts: tagPostCount, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllTagsPageContext) XCTAssertEqual(context.tags.first?.postCount, 20) @@ -86,7 +81,7 @@ class BlogPresenterTests: XCTestCase { let tags = [tag1, tag2] let tagPostCount = [0: 0, 1: 20] let pageInformation = buildPageInformation(currentPageURL: allTagsURL) - _ = presenter.allTagsView(on: basicContainer, tags: tags, tagPostCounts: tagPostCount, pageInformation: pageInformation) + _ = presenter.allTagsView(tags: tags, tagPostCounts: tagPostCount, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllTagsPageContext) XCTAssertEqual(context.tags[1].tagID, 0) @@ -96,7 +91,7 @@ class BlogPresenterTests: XCTestCase { func testTwitterHandleNotSetOnAllTagsPageIfNotGiven() throws { let tags = [BlogTag(id: 0, name: "tag1"), BlogTag(id: 1, name: "tag2")] let pageInformation = buildPageInformation(currentPageURL: allTagsURL, siteTwitterHandle: nil) - _ = presenter.allTagsView(on: basicContainer, tags: tags, tagPostCounts: [:], pageInformation: pageInformation) + _ = presenter.allTagsView(tags: tags, tagPostCounts: [:], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllTagsPageContext) XCTAssertNil(context.pageInformation.siteTwitterHandle) @@ -105,7 +100,7 @@ class BlogPresenterTests: XCTestCase { func testDisqusNameNotSetOnAllTagsPageIfNotGiven() throws { let tags = [BlogTag(id: 0, name: "tag1"), BlogTag(id: 1, name: "tag2")] let pageInformation = buildPageInformation(currentPageURL: allTagsURL, disqusName: nil) - _ = presenter.allTagsView(on: basicContainer, tags: tags, tagPostCounts: [:], pageInformation: pageInformation) + _ = presenter.allTagsView(tags: tags, tagPostCounts: [:], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllTagsPageContext) XCTAssertNil(context.pageInformation.disqusName) @@ -114,7 +109,7 @@ class BlogPresenterTests: XCTestCase { func testGAIdentifierNotSetOnAllTagsPageIfNotGiven() throws { let tags = [BlogTag(id: 0, name: "tag1"), BlogTag(id: 1, name: "tag2")] let pageInformation = buildPageInformation(currentPageURL: allTagsURL, googleAnalyticsIdentifier: nil) - _ = presenter.allTagsView(on: basicContainer, tags: tags, tagPostCounts: [:], pageInformation: pageInformation) + _ = presenter.allTagsView(tags: tags, tagPostCounts: [:], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllTagsPageContext) XCTAssertNil(context.pageInformation.googleAnalyticsIdentifier) @@ -124,7 +119,7 @@ class BlogPresenterTests: XCTestCase { let tags = [BlogTag(id: 0, name: "tag1"), BlogTag(id: 1, name: "tag2")] let user = TestDataBuilder.anyUser() let pageInformation = buildPageInformation(currentPageURL: allTagsURL, user: user) - _ = presenter.allTagsView(on: basicContainer, tags: tags, tagPostCounts: [:], pageInformation: pageInformation) + _ = presenter.allTagsView(tags: tags, tagPostCounts: [:], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllTagsPageContext) XCTAssertEqual(context.pageInformation.loggedInUser?.name, user.name) @@ -138,7 +133,7 @@ class BlogPresenterTests: XCTestCase { let user2 = TestDataBuilder.anyUser(id: 1, name: "Han", username: "han") let authors = [user1, user2] let pageInformation = buildPageInformation(currentPageURL: allAuthorsURL) - _ = presenter.allAuthorsView(on: basicContainer, authors: authors, authorPostCounts: [:], pageInformation: pageInformation) + _ = presenter.allAuthorsView(authors: authors, authorPostCounts: [:], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllAuthorsPageContext) XCTAssertEqual(context.authors.count, 2) @@ -158,7 +153,7 @@ class BlogPresenterTests: XCTestCase { let authors = [user1, user2] let authorPostCount = [0: 1, 1: 20] let pageInformation = buildPageInformation(currentPageURL: allAuthorsURL) - _ = presenter.allAuthorsView(on: basicContainer, authors: authors, authorPostCounts: authorPostCount, pageInformation: pageInformation) + _ = presenter.allAuthorsView(authors: authors, authorPostCounts: authorPostCount, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllAuthorsPageContext) XCTAssertEqual(context.authors.first?.postCount, 20) @@ -173,7 +168,7 @@ class BlogPresenterTests: XCTestCase { let authors = [user1, user2] let authorPostCount = [0: 0, 1: 20] let pageInformation = buildPageInformation(currentPageURL: allAuthorsURL) - _ = presenter.allAuthorsView(on: basicContainer, authors: authors, authorPostCounts: authorPostCount, pageInformation: pageInformation) + _ = presenter.allAuthorsView(authors: authors, authorPostCounts: authorPostCount, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllAuthorsPageContext) XCTAssertEqual(context.authors[1].userID, 0) @@ -182,7 +177,7 @@ class BlogPresenterTests: XCTestCase { func testTwitterHandleNotSetOnAllAuthorsPageIfNotProvided() throws { let pageInformation = buildPageInformation(currentPageURL: allAuthorsURL, siteTwitterHandle: nil) - _ = presenter.allAuthorsView(on: basicContainer, authors: [], authorPostCounts: [:], pageInformation: pageInformation) + _ = presenter.allAuthorsView(authors: [], authorPostCounts: [:], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllAuthorsPageContext) XCTAssertNil(context.pageInformation.siteTwitterHandle) @@ -190,7 +185,7 @@ class BlogPresenterTests: XCTestCase { func testDisqusNameNotSetOnAllAuthorsPageIfNotProvided() throws { let pageInformation = buildPageInformation(currentPageURL: allAuthorsURL, disqusName: nil) - _ = presenter.allAuthorsView(on: basicContainer, authors: [], authorPostCounts: [:], pageInformation: pageInformation) + _ = presenter.allAuthorsView(authors: [], authorPostCounts: [:], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllAuthorsPageContext) XCTAssertNil(context.pageInformation.disqusName) @@ -198,7 +193,7 @@ class BlogPresenterTests: XCTestCase { func testGAIdentifierNotSetOnAllAuthorsPageIfNotProvided() throws { let pageInformation = buildPageInformation(currentPageURL: allAuthorsURL, googleAnalyticsIdentifier: nil) - _ = presenter.allAuthorsView(on: basicContainer, authors: [], authorPostCounts: [:], pageInformation: pageInformation) + _ = presenter.allAuthorsView(authors: [], authorPostCounts: [:], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllAuthorsPageContext) XCTAssertNil(context.pageInformation.googleAnalyticsIdentifier) @@ -207,7 +202,7 @@ class BlogPresenterTests: XCTestCase { func testLoggedInUserPassedToAllAuthorsPageIfProvided() throws { let user = TestDataBuilder.anyUser() let pageInformation = buildPageInformation(currentPageURL: allAuthorsURL, user: user) - _ = presenter.allAuthorsView(on: basicContainer, authors: [], authorPostCounts: [:], pageInformation: pageInformation) + _ = presenter.allAuthorsView(authors: [], authorPostCounts: [:], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AllAuthorsPageContext) XCTAssertEqual(context.pageInformation.loggedInUser?.name, user.name) @@ -228,7 +223,7 @@ class BlogPresenterTests: XCTestCase { let totalPages = 10 let currentQuery = "?page=2" - _ = presenter.tagView(on: basicContainer, tag: testTag, posts: posts, authors: [user], totalPosts: 3, pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation(currentPage: currentPage, totalPages: totalPages, currentQuery: currentQuery)) + _ = presenter.tagView(tag: testTag, posts: posts, authors: [user], totalPosts: 3, pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation(currentPage: currentPage, totalPages: totalPages, currentQuery: currentQuery)) let context = try XCTUnwrap(viewRenderer.capturedContext as? TagPageContext) XCTAssertEqual(context.tag.name, testTag.name) @@ -256,7 +251,7 @@ class BlogPresenterTests: XCTestCase { func testNoLoggedInUserPassedToTagPageIfNoneProvided() throws { let pageInformation = buildPageInformation(currentPageURL: tagURL) - _ = presenter.tagView(on: basicContainer, tag: testTag, posts: [], authors: [], totalPosts: 0, pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.tagView(tag: testTag, posts: [], authors: [], totalPosts: 0, pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? TagPageContext) XCTAssertNil(context.pageInformation.loggedInUser) @@ -264,7 +259,7 @@ class BlogPresenterTests: XCTestCase { func testDisqusNameNotPassedToTagPageIfNotSet() throws { let pageInformation = buildPageInformation(currentPageURL: tagURL, disqusName: nil) - _ = presenter.tagView(on: basicContainer, tag: testTag, posts: [], authors: [], totalPosts: 0, pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.tagView(tag: testTag, posts: [], authors: [], totalPosts: 0, pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? TagPageContext) XCTAssertNil(context.pageInformation.disqusName) @@ -272,7 +267,7 @@ class BlogPresenterTests: XCTestCase { func testTwitterHandleNotPassedToTagPageIfNotSet() throws { let pageInformation = buildPageInformation(currentPageURL: tagURL, siteTwitterHandle: nil) - _ = presenter.tagView(on: basicContainer, tag: testTag, posts: [], authors: [], totalPosts: 0, pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.tagView(tag: testTag, posts: [], authors: [], totalPosts: 0, pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? TagPageContext) XCTAssertNil(context.pageInformation.siteTwitterHandle) @@ -280,7 +275,7 @@ class BlogPresenterTests: XCTestCase { func testGAIdentifierNotPassedToTagPageIfNotSet() throws { let pageInformation = buildPageInformation(currentPageURL: tagURL, googleAnalyticsIdentifier: nil) - _ = presenter.tagView(on: basicContainer, tag: testTag, posts: [], authors: [], totalPosts: 0, pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.tagView(tag: testTag, posts: [], authors: [], totalPosts: 0, pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? TagPageContext) XCTAssertNil(context.pageInformation.googleAnalyticsIdentifier) @@ -306,7 +301,7 @@ class BlogPresenterTests: XCTestCase { let currentQuery = "?page=2" let pageInformation = buildPageInformation(currentPageURL: blogIndexURL) - _ = presenter.indexView(on: basicContainer, posts: [post, post2], tags: tags, authors: [author1, author2], tagsForPosts: [1: [tag1, tag2], 2: [tag1]], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation(currentPage: currentPage, totalPages: totalPages, currentQuery: currentQuery)) + _ = presenter.indexView(posts: [post, post2], tags: tags, authors: [author1, author2], tagsForPosts: [1: [tag1, tag2], 2: [tag1]], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation(currentPage: currentPage, totalPages: totalPages, currentQuery: currentQuery)) let context = try XCTUnwrap(viewRenderer.capturedContext as? BlogIndexPageContext) XCTAssertEqual(context.title, "Blog") @@ -353,7 +348,7 @@ class BlogPresenterTests: XCTestCase { func testUserPassedToBlogIndexIfUserPassedIn() throws { let user = TestDataBuilder.anyUser() let pageInformation = buildPageInformation(currentPageURL: blogIndexURL, user: user) - _ = presenter.indexView(on: basicContainer, posts: [], tags: [], authors: [], tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.indexView(posts: [], tags: [], authors: [], tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? BlogIndexPageContext) XCTAssertEqual(context.pageInformation.loggedInUser?.name, user.name) @@ -362,7 +357,7 @@ class BlogPresenterTests: XCTestCase { func testDisqusNameNotPassedToBlogIndexIfNotPassedIn() throws { let pageInformation = buildPageInformation(currentPageURL: blogIndexURL, disqusName: nil) - _ = presenter.indexView(on: basicContainer, posts: [], tags: [], authors: [], tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.indexView(posts: [], tags: [], authors: [], tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? BlogIndexPageContext) XCTAssertNil(context.pageInformation.disqusName) @@ -370,7 +365,7 @@ class BlogPresenterTests: XCTestCase { func testTwitterHandleNotPassedToBlogIndexIfNotPassedIn() throws { let pageInformation = buildPageInformation(currentPageURL: blogIndexURL, siteTwitterHandle: nil) - _ = presenter.indexView(on: basicContainer, posts: [], tags: [], authors: [], tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.indexView(posts: [], tags: [], authors: [], tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? BlogIndexPageContext) XCTAssertNil(context.pageInformation.siteTwitterHandle) @@ -378,7 +373,7 @@ class BlogPresenterTests: XCTestCase { func testGAIdentifierNotPassedToBlogIndexIfNotPassedIn() throws { let pageInformation = buildPageInformation(currentPageURL: blogIndexURL, googleAnalyticsIdentifier: nil) - _ = presenter.indexView(on: basicContainer, posts: [], tags: [], authors: [], tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.indexView(posts: [], tags: [], authors: [], tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? BlogIndexPageContext) XCTAssertNil(context.pageInformation.googleAnalyticsIdentifier) @@ -397,7 +392,7 @@ class BlogPresenterTests: XCTestCase { let query = "page=2" let pageInformation = buildPageInformation(currentPageURL: authorURL) - _ = presenter.authorView(on: basicContainer, author: author, posts: [post1, post2], postCount: 2, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation(currentPage: page, totalPages: totalPages, currentQuery: query)) + _ = presenter.authorView(author: author, posts: [post1, post2], postCount: 2, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation(currentPage: page, totalPages: totalPages, currentQuery: query)) let context = try XCTUnwrap(viewRenderer.capturedContext as? AuthorPageContext) XCTAssertEqual(context.author.name, author.name) @@ -426,7 +421,7 @@ class BlogPresenterTests: XCTestCase { let author = TestDataBuilder.anyUser(id: 0) let user = TestDataBuilder.anyUser(id: 1, username: "hans") let pageInformation = buildPageInformation(currentPageURL: authorURL, user: user) - _ = presenter.authorView(on: basicContainer, author: author, posts: [], postCount: 0, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.authorView(author: author, posts: [], postCount: 0, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? AuthorPageContext) XCTAssertEqual(context.pageInformation.loggedInUser?.userID, user.userID) @@ -436,7 +431,7 @@ class BlogPresenterTests: XCTestCase { func testMyProfileFlagSetIfLoggedInUserIsTheSameAsAuthorOnAuthorView() throws { let author = TestDataBuilder.anyUser(id: 0) let pageInformation = buildPageInformation(currentPageURL: authorURL, user: author) - _ = presenter.authorView(on: basicContainer, author: author, posts: [], postCount: 0, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.authorView(author: author, posts: [], postCount: 0, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? AuthorPageContext) XCTAssertTrue(context.myProfile) @@ -445,7 +440,7 @@ class BlogPresenterTests: XCTestCase { func testAuthorViewDoesNotGetDisqusNameIfNotProvided() throws { let author = TestDataBuilder.anyUser() let pageInformation = buildPageInformation(currentPageURL: authorURL, disqusName: nil) - _ = presenter.authorView(on: basicContainer, author: author, posts: [], postCount: 0, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.authorView(author: author, posts: [], postCount: 0, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? AuthorPageContext) XCTAssertNil(context.pageInformation.disqusName) @@ -454,7 +449,7 @@ class BlogPresenterTests: XCTestCase { func testAuthorViewDoesNotGetTwitterHandleIfNotProvided() throws { let author = TestDataBuilder.anyUser() let pageInformation = buildPageInformation(currentPageURL: authorURL, siteTwitterHandle: nil) - _ = presenter.authorView(on: basicContainer, author: author, posts: [], postCount: 0, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.authorView(author: author, posts: [], postCount: 0, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? AuthorPageContext) XCTAssertNil(context.pageInformation.siteTwitterHandle) @@ -463,7 +458,7 @@ class BlogPresenterTests: XCTestCase { func testAuthorViewDoesNotGetGAIdentifierIfNotProvided() throws { let author = TestDataBuilder.anyUser() let pageInformation = buildPageInformation(currentPageURL: authorURL, googleAnalyticsIdentifier: nil) - _ = presenter.authorView(on: basicContainer, author: author, posts: [], postCount: 0, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.authorView(author: author, posts: [], postCount: 0, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? AuthorPageContext) XCTAssertNil(context.pageInformation.googleAnalyticsIdentifier) @@ -478,7 +473,7 @@ class BlogPresenterTests: XCTestCase { let post3 = try TestDataBuilder.anyPost(author: author) post3.blogID = 3 let pageInformation = buildPageInformation(currentPageURL: authorURL) - _ = presenter.authorView(on: basicContainer, author: author, posts: [post1, post2, post3], postCount: 3, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.authorView(author: author, posts: [post1, post2, post3], postCount: 3, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? AuthorPageContext) XCTAssertEqual(context.postCount, 3) @@ -489,7 +484,7 @@ class BlogPresenterTests: XCTestCase { let post1 = try TestDataBuilder.anyPost(author: author, contents: TestDataBuilder.longContents) post1.blogID = 1 let pageInformation = buildPageInformation(currentPageURL: authorURL) - _ = presenter.authorView(on: basicContainer, author: author, posts: [post1], postCount: 1, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) + _ = presenter.authorView(author: author, posts: [post1], postCount: 1, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: buildPaginationInformation()) let context = try XCTUnwrap(viewRenderer.capturedContext as? AuthorPageContext) let characterCount = try XCTUnwrap(context.posts.first?.longSnippet.count) @@ -498,7 +493,7 @@ class BlogPresenterTests: XCTestCase { func testLoginViewGetsCorrectParameters() throws { let pageInformation = buildPageInformation(currentPageURL: loginURL) - _ = presenter.loginView(on: basicContainer, loginWarning: false, errors: nil, username: nil, usernameError: false, passwordError: false, rememberMe: false, pageInformation: pageInformation) + _ = presenter.loginView(loginWarning: false, errors: nil, username: nil, usernameError: false, passwordError: false, rememberMe: false, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? LoginPageContext) XCTAssertNil(context.errors) @@ -515,7 +510,7 @@ class BlogPresenterTests: XCTestCase { func testLoginViewWhenErrored() throws { let expectedError = "Username/password incorrect" let pageInformation = buildPageInformation(currentPageURL: loginURL) - _ = presenter.loginView(on: basicContainer, loginWarning: true, errors: [expectedError], username: "tim", usernameError: true, passwordError: true, rememberMe: true, pageInformation: pageInformation) + _ = presenter.loginView(loginWarning: true, errors: [expectedError], username: "tim", usernameError: true, passwordError: true, rememberMe: true, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? LoginPageContext) XCTAssertEqual(context.errors?.count, 1) @@ -536,7 +531,7 @@ class BlogPresenterTests: XCTestCase { let pageInformation = buildPageInformation(currentPageURL: searchURL) let paginationInformation = PaginationTagInformation(currentPage: 1, totalPages: 3, currentQuery: "?term=vapor") - _ = presenter.searchView(on: basicContainer, totalResults: 2, posts: [post1, post2], authors: [author], searchTerm: "vapor", tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: paginationInformation) + _ = presenter.searchView(totalResults: 2, posts: [post1, post2], authors: [author], searchTerm: "vapor", tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: paginationInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? SearchPageContext) XCTAssertEqual(context.title, "Search Blog") @@ -562,7 +557,7 @@ class BlogPresenterTests: XCTestCase { func testSearchPageGetsNilIfNoSearchTermProvided() throws { let pageInformation = buildPageInformation(currentPageURL: searchURL) let paginationInformation = PaginationTagInformation(currentPage: 0, totalPages: 0, currentQuery: nil) - _ = presenter.searchView(on: basicContainer, totalResults: 0, posts: [], authors: [], searchTerm: nil, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: paginationInformation) + _ = presenter.searchView(totalResults: 0, posts: [], authors: [], searchTerm: nil, tagsForPosts: [:], pageInformation: pageInformation, paginationTagInfo: paginationInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? SearchPageContext) XCTAssertNil(context.searchTerm) From 9a574536a1d9970cf1b8d141887eaac35e8dc89b Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 3 Apr 2020 17:12:39 +0100 Subject: [PATCH 32/70] Get BlogViewTests compiling --- .../ViewTests/BlogViewTests.swift | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift index e80f0d90..f4b88d10 100644 --- a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift @@ -5,7 +5,7 @@ import Vapor class BlogViewTests: XCTestCase { // MARK: - Properties - var basicContainer: BasicContainer! + var eventLoopGroup: MultiThreadedEventLoopGroup! var presenter: ViewBlogPresenter! var author: BlogUser! var post: BlogPost! @@ -17,14 +17,9 @@ class BlogViewTests: XCTestCase { // MARK: - Overrides override func setUp() { - presenter = ViewBlogPresenter() - basicContainer = BasicContainer(config: Config.default(), environment: Environment.testing, services: .init(), on: EmbeddedEventLoop()) - basicContainer.services.register(ViewRenderer.self) { _ in - return self.viewRenderer - } - basicContainer.services.register(LongPostDateFormatter.self) - basicContainer.services.register(NumericPostDateFormatter.self) - viewRenderer = CapturingViewRenderer(worker: basicContainer) + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + let viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) + presenter = ViewBlogPresenter(viewRenderer: viewRenderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: eventLoopGroup) author = TestDataBuilder.anyUser() author.userID = 1 let createdDate = Date(timeIntervalSince1970: 1584714638) @@ -36,13 +31,13 @@ class BlogViewTests: XCTestCase { } override func tearDown() { - try! basicContainer.syncShutdownGracefully() + try! eventLoopGroup.syncShutdownGracefully() } // MARK: - Tests func testDescriptionOnBlogPostPageIsShortSnippetTextCleaned() throws { - _ = presenter.postView(on: basicContainer, post: post, author: author, tags: [], pageInformation: pageInformation) + _ = presenter.postView(post: post, author: author, tags: [], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? BlogPostPageContext) let expectedDescription = "Welcome to SteamPress!\nSteamPress 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!" @@ -51,7 +46,7 @@ class BlogViewTests: XCTestCase { func testBlogPostPageGetsCorrectParameters() throws { let tag = BlogTag(id: 1, name: "Engineering") - _ = presenter.postView(on: basicContainer, post: post, author: author, tags: [tag], pageInformation: pageInformation) + _ = presenter.postView(post: post, author: author, tags: [tag], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? BlogPostPageContext) @@ -89,7 +84,7 @@ class BlogViewTests: XCTestCase { func testDisqusNameNotPassedToBlogPostPageIfNotPassedIn() throws { let pageInformationWithoutDisqus = BlogGlobalPageInformation(disqusName: nil, siteTwitterHandle: "twitter", googleAnalyticsIdentifier: "google", loggedInUser: author, websiteURL: websiteURL, currentPageURL: currentPageURL, currentPageEncodedURL: currentPageURL.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!) - _ = presenter.postView(on: basicContainer, post: post, author: author, tags: [], pageInformation: pageInformationWithoutDisqus) + _ = presenter.postView(post: post, author: author, tags: [], pageInformation: pageInformationWithoutDisqus) let context = try XCTUnwrap(viewRenderer.capturedContext as? BlogPostPageContext) XCTAssertNil(context.pageInformation.disqusName) @@ -97,7 +92,7 @@ class BlogViewTests: XCTestCase { func testTwitterHandleNotPassedToBlogPostPageIfNotPassedIn() throws { let pageInformationWithoutTwitterHandle = BlogGlobalPageInformation(disqusName: "disqus", siteTwitterHandle: nil, googleAnalyticsIdentifier: "google", loggedInUser: author, websiteURL: websiteURL, currentPageURL: currentPageURL, currentPageEncodedURL: currentPageURL.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!) - _ = presenter.postView(on: basicContainer, post: post, author: author, tags: [], pageInformation: pageInformationWithoutTwitterHandle) + _ = presenter.postView(post: post, author: author, tags: [], pageInformation: pageInformationWithoutTwitterHandle) let context = try XCTUnwrap(viewRenderer.capturedContext as? BlogPostPageContext) XCTAssertNil(context.pageInformation.siteTwitterHandle) @@ -105,7 +100,7 @@ class BlogViewTests: XCTestCase { func testGAIdentifierNotPassedToBlogPostPageIfNotPassedIn() throws { let pageInformationWithoutGAIdentifier = BlogGlobalPageInformation(disqusName: "disqus", siteTwitterHandle: "twitter", googleAnalyticsIdentifier: nil, loggedInUser: author, websiteURL: websiteURL, currentPageURL: currentPageURL, currentPageEncodedURL: currentPageURL.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!) - _ = presenter.postView(on: basicContainer, post: post, author: author, tags: [], pageInformation: pageInformationWithoutGAIdentifier) + _ = presenter.postView(post: post, author: author, tags: [], pageInformation: pageInformationWithoutGAIdentifier) let context = try XCTUnwrap(viewRenderer.capturedContext as? BlogPostPageContext) XCTAssertNil(context.pageInformation.googleAnalyticsIdentifier) @@ -116,7 +111,7 @@ class BlogViewTests: XCTestCase { let urlEncodedName = try XCTUnwrap(tagName.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)) let tag = BlogTag(id: 1, name: tagName) - _ = presenter.postView(on: basicContainer, post: post, author: author, tags: [tag], pageInformation: pageInformation) + _ = presenter.postView(post: post, author: author, tags: [tag], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? BlogPostPageContext) XCTAssertEqual(context.post.tags.first?.urlEncodedName, urlEncodedName) From d9220a5b7b48b78ae2172e98f94ba7235c3dd9e0 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 3 Apr 2020 17:16:23 +0100 Subject: [PATCH 33/70] Get BlogAdminPresenterTests compiling --- .../ViewTests/BlogAdminPresenterTests.swift | 45 +++++++++---------- .../ViewTests/BlogPresenterTests.swift | 8 ++-- .../ViewTests/BlogViewTests.swift | 8 ++-- 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/Tests/SteamPressTests/ViewTests/BlogAdminPresenterTests.swift b/Tests/SteamPressTests/ViewTests/BlogAdminPresenterTests.swift index 60d6e3af..8090cfcb 100644 --- a/Tests/SteamPressTests/ViewTests/BlogAdminPresenterTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogAdminPresenterTests.swift @@ -5,7 +5,7 @@ import Vapor class BlogAdminPresenterTests: XCTestCase { // MARK: - Properties - var basicContainer: BasicContainer! + var eventLoopGroup: MultiThreadedEventLoopGroup! var presenter: ViewBlogAdminPresenter! var viewRenderer: CapturingViewRenderer! @@ -25,18 +25,13 @@ class BlogAdminPresenterTests: XCTestCase { // MARK: - Overrides override func setUp() { - presenter = ViewBlogAdminPresenter(pathCreator: BlogPathCreator(blogPath: "blog")) - basicContainer = BasicContainer(config: Config.default(), environment: Environment.testing, services: .init(), on: EmbeddedEventLoop()) - basicContainer.services.register(ViewRenderer.self) { _ in - return self.viewRenderer - } - basicContainer.services.register(LongPostDateFormatter.self) - basicContainer.services.register(NumericPostDateFormatter.self) - viewRenderer = CapturingViewRenderer(worker: basicContainer) + eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) + presenter = ViewBlogAdminPresenter(pathCreator: BlogPathCreator(blogPath: "blog"), viewRenderer: viewRenderer, eventLoopGroup: eventLoopGroup, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) } - override func tearDown() { - try! basicContainer.syncShutdownGracefully() + override func tearDownWithError() throws { + try eventLoopGroup.syncShutdownGracefully() } // MARK: - Tests @@ -45,7 +40,7 @@ class BlogAdminPresenterTests: XCTestCase { func testPasswordViewGivenCorrectParameters() throws { let pageInformation = buildPageInformation(currentPageURL: resetPasswordURL) - _ = presenter.createResetPasswordView(on: basicContainer, errors: nil, passwordError: nil, confirmPasswordError: nil, pageInformation: pageInformation) + _ = presenter.createResetPasswordView(errors: nil, passwordError: nil, confirmPasswordError: nil, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? ResetPasswordPageContext) XCTAssertNil(context.errors) @@ -60,7 +55,7 @@ class BlogAdminPresenterTests: XCTestCase { func testPasswordViewHasCorrectParametersWhenError() throws { let expectedError = "Passwords do not match" let pageInformation = buildPageInformation(currentPageURL: resetPasswordURL) - _ = presenter.createResetPasswordView(on: basicContainer, errors: [expectedError], passwordError: true, confirmPasswordError: true, pageInformation: pageInformation) + _ = presenter.createResetPasswordView(errors: [expectedError], passwordError: true, confirmPasswordError: true, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? ResetPasswordPageContext) XCTAssertEqual(context.errors?.count, 1) @@ -78,7 +73,7 @@ class BlogAdminPresenterTests: XCTestCase { let post = try TestDataBuilder.anyPost(author: currentUser) let pageInformation = buildPageInformation(currentPageURL: adminPageURL) - _ = presenter.createIndexView(on: basicContainer, posts: [draftPost, post], users: [currentUser], errors: nil, pageInformation: pageInformation) + _ = presenter.createIndexView(posts: [draftPost, post], users: [currentUser], errors: nil, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AdminPageContext) @@ -101,7 +96,7 @@ class BlogAdminPresenterTests: XCTestCase { func testAdminPageWithErrors() throws { let expectedError = "You cannot delete yourself!" let pageInformation = buildPageInformation(currentPageURL: adminPageURL) - _ = presenter.createIndexView(on: basicContainer, posts: [], users: [], errors: [expectedError], pageInformation: pageInformation) + _ = presenter.createIndexView(posts: [], users: [], errors: [expectedError], pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? AdminPageContext) XCTAssertEqual(context.errors?.first, expectedError) @@ -111,7 +106,7 @@ class BlogAdminPresenterTests: XCTestCase { func testCreateUserViewGetsCorrectParameters() throws { let pageInformation = buildPageInformation(currentPageURL: createUserPageURL) - _ = presenter.createUserView(on: basicContainer, editing: false, errors: nil, name: nil, nameError: false, username: nil, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: false, userID: nil, profilePicture: nil, twitterHandle: nil, biography: nil, tagline: nil, pageInformation: pageInformation) + _ = presenter.createUserView(editing: false, errors: nil, name: nil, nameError: false, username: nil, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: false, userID: nil, profilePicture: nil, twitterHandle: nil, biography: nil, tagline: nil, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? CreateUserPageContext) @@ -147,7 +142,7 @@ class BlogAdminPresenterTests: XCTestCase { let expectedTagline = "A son without a father" let pageInformation = buildPageInformation(currentPageURL: createUserPageURL) - _ = presenter.createUserView(on: basicContainer, editing: false, errors: [expectedError], name: expectedName, nameError: false, username: expectedUsername, usernameErorr: false, passwordError: true, confirmPasswordError: true, resetPasswordOnLogin: true, userID: nil, profilePicture: expectedProfilePicture, twitterHandle: expectedTwitterHandler, biography: expectedBiography, tagline: expectedTagline, pageInformation: pageInformation) + _ = presenter.createUserView(editing: false, errors: [expectedError], name: expectedName, nameError: false, username: expectedUsername, usernameErorr: false, passwordError: true, confirmPasswordError: true, resetPasswordOnLogin: true, userID: nil, profilePicture: expectedProfilePicture, twitterHandle: expectedTwitterHandler, biography: expectedBiography, tagline: expectedTagline, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? CreateUserPageContext) XCTAssertEqual(context.errors?.count, 1) @@ -169,7 +164,7 @@ class BlogAdminPresenterTests: XCTestCase { let expectedError = "No name supplied" let pageInformation = buildPageInformation(currentPageURL: createUserPageURL) - _ = presenter.createUserView(on: basicContainer, editing: false, errors: [expectedError], name: nil, nameError: true, username: nil, usernameErorr: true, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: true, userID: nil, profilePicture: nil, twitterHandle: nil, biography: nil, tagline: nil, pageInformation: pageInformation) + _ = presenter.createUserView(editing: false, errors: [expectedError], name: nil, nameError: true, username: nil, usernameErorr: true, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: true, userID: nil, profilePicture: nil, twitterHandle: nil, biography: nil, tagline: nil, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? CreateUserPageContext) XCTAssertNil(context.nameSupplied) @@ -180,7 +175,7 @@ class BlogAdminPresenterTests: XCTestCase { func testCreateUserViewForEditing() throws { let pageInformation = buildPageInformation(currentPageURL: editUserPageURL) - _ = presenter.createUserView(on: basicContainer, editing: true, errors: nil, name: currentUser.name, nameError: false, username: currentUser.username, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: false, userID: currentUser.userID, profilePicture: currentUser.profilePicture, twitterHandle: currentUser.twitterHandle, biography: currentUser.biography, tagline: currentUser.tagline, pageInformation: pageInformation) + _ = presenter.createUserView(editing: true, errors: nil, name: currentUser.name, nameError: false, username: currentUser.username, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: false, userID: currentUser.userID, profilePicture: currentUser.profilePicture, twitterHandle: currentUser.twitterHandle, biography: currentUser.biography, tagline: currentUser.tagline, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? CreateUserPageContext) XCTAssertEqual(context.nameSupplied, currentUser.name) XCTAssertFalse(context.nameError) @@ -207,7 +202,7 @@ class BlogAdminPresenterTests: XCTestCase { var errored = false do { - _ = try presenter.createUserView(on: basicContainer, editing: true, errors: [], name: currentUser.name, nameError: false, username: currentUser.username, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: false, userID: nil, profilePicture: currentUser.profilePicture, twitterHandle: currentUser.twitterHandle, biography: currentUser.biography, tagline: currentUser.tagline, pageInformation: pageInformation).wait() + _ = try presenter.createUserView(editing: true, errors: [], name: currentUser.name, nameError: false, username: currentUser.username, usernameErorr: false, passwordError: false, confirmPasswordError: false, resetPasswordOnLogin: false, userID: nil, profilePicture: currentUser.profilePicture, twitterHandle: currentUser.twitterHandle, biography: currentUser.biography, tagline: currentUser.tagline, pageInformation: pageInformation).wait() } catch { errored = true } @@ -218,7 +213,7 @@ class BlogAdminPresenterTests: XCTestCase { func testCreateBlogPostViewGetsCorrectParameters() throws { let pageInformation = buildPageInformation(currentPageURL: createBlogPageURL) - _ = presenter.createPostView(on: basicContainer, errors: nil, title: nil, contents: nil, slugURL: nil, tags: nil, isEditing: false, post: nil, isDraft: nil, titleError: false, contentsError: false, pageInformation: pageInformation) + _ = presenter.createPostView(errors: nil, title: nil, contents: nil, slugURL: nil, tags: nil, isEditing: false, post: nil, isDraft: nil, titleError: false, contentsError: false, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? CreatePostPageContext) @@ -246,7 +241,7 @@ class BlogAdminPresenterTests: XCTestCase { let expectedError = "Please enter a title" let pageInformation = buildPageInformation(currentPageURL: createBlogPageURL) - _ = presenter.createPostView(on: basicContainer, errors: [expectedError], title: nil, contents: nil, slugURL: nil, tags: nil, isEditing: false, post: nil, isDraft: nil, titleError: true, contentsError: true, pageInformation: pageInformation) + _ = presenter.createPostView(errors: [expectedError], title: nil, contents: nil, slugURL: nil, tags: nil, isEditing: false, post: nil, isDraft: nil, titleError: true, contentsError: true, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? CreatePostPageContext) @@ -265,7 +260,7 @@ class BlogAdminPresenterTests: XCTestCase { let tag = "Engineering" let pageInformation = buildPageInformation(currentPageURL: editPostPageURL) - _ = presenter.createPostView(on: basicContainer, errors: nil, title: postToEdit.title, contents: postToEdit.contents, slugURL: postToEdit.slugUrl, tags: [tag], isEditing: true, post: postToEdit, isDraft: false, titleError: false, contentsError: false, pageInformation: pageInformation) + _ = presenter.createPostView(errors: nil, title: postToEdit.title, contents: postToEdit.contents, slugURL: postToEdit.slugUrl, tags: [tag], isEditing: true, post: postToEdit, isDraft: false, titleError: false, contentsError: false, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? CreatePostPageContext) @@ -295,7 +290,7 @@ class BlogAdminPresenterTests: XCTestCase { var errored = false do { let pageInformation = buildPageInformation(currentPageURL: editPostPageURL) - _ = try presenter.createPostView(on: basicContainer, errors: nil, title: nil, contents: nil, slugURL: nil, tags: nil, isEditing: true, post: nil, isDraft: nil, titleError: false, contentsError: false, pageInformation: pageInformation).wait() + _ = try presenter.createPostView(errors: nil, title: nil, contents: nil, slugURL: nil, tags: nil, isEditing: true, post: nil, isDraft: nil, titleError: false, contentsError: false, pageInformation: pageInformation).wait() } catch { errored = true } @@ -307,7 +302,7 @@ class BlogAdminPresenterTests: XCTestCase { let draftPost = try TestDataBuilder.anyPost(author: currentUser, published: false) let pageInformation = buildPageInformation(currentPageURL: editPostPageURL) - _ = presenter.createPostView(on: basicContainer, errors: nil, title: draftPost.title, contents: draftPost.contents, slugURL: draftPost.slugUrl, tags: nil, isEditing: true, post: draftPost, isDraft: true, titleError: false, contentsError: false, pageInformation: pageInformation) + _ = presenter.createPostView(errors: nil, title: draftPost.title, contents: draftPost.contents, slugURL: draftPost.slugUrl, tags: nil, isEditing: true, post: draftPost, isDraft: true, titleError: false, contentsError: false, pageInformation: pageInformation) let context = try XCTUnwrap(viewRenderer.capturedContext as? CreatePostPageContext) XCTAssertTrue(context.draft) diff --git a/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift b/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift index d5c1cf0c..cef59a96 100644 --- a/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift @@ -26,14 +26,14 @@ class BlogPresenterTests: XCTestCase { // MARK: - Overrides override func setUp() { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - let viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) + eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) presenter = ViewBlogPresenter(viewRenderer: viewRenderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: eventLoopGroup) testTag = BlogTag(id: 1, name: "Tattoine") } - override func tearDown() { - try! eventLoopGroup.syncShutdownGracefully() + override func tearDownWithError() throws { + try eventLoopGroup.syncShutdownGracefully() } // MARK: - Tests diff --git a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift index f4b88d10..9e21016b 100644 --- a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift @@ -17,8 +17,8 @@ class BlogViewTests: XCTestCase { // MARK: - Overrides override func setUp() { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - let viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) + eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) presenter = ViewBlogPresenter(viewRenderer: viewRenderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: eventLoopGroup) author = TestDataBuilder.anyUser() author.userID = 1 @@ -30,8 +30,8 @@ class BlogViewTests: XCTestCase { pageInformation = BlogGlobalPageInformation(disqusName: "disqusName", siteTwitterHandle: "twitterHandleSomething", googleAnalyticsIdentifier: "GAString....", loggedInUser: author, websiteURL: websiteURL, currentPageURL: currentPageURL, currentPageEncodedURL: currentPageURL.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!) } - override func tearDown() { - try! eventLoopGroup.syncShutdownGracefully() + override func tearDownWithError() throws { + try eventLoopGroup.syncShutdownGracefully() } // MARK: - Tests From 7cb9b0daf0f2619c197730471c9dec2bb884c62e Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 3 Apr 2020 17:24:30 +0100 Subject: [PATCH 34/70] Get the login tests working --- .../AdminTests/LoginTests.swift | 22 +++++------- Tests/SteamPressTests/Helpers/TestWorld.swift | 35 ++++++++++--------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/Tests/SteamPressTests/AdminTests/LoginTests.swift b/Tests/SteamPressTests/AdminTests/LoginTests.swift index 605d1f61..fb50ada4 100644 --- a/Tests/SteamPressTests/AdminTests/LoginTests.swift +++ b/Tests/SteamPressTests/AdminTests/LoginTests.swift @@ -33,7 +33,7 @@ class LoginTests: XCTestCase { func testLogin() throws { testWorld = try TestWorld.create(path: "blog", passwordHasherToUse: .real) - let hashedPassword = try BCrypt.hash("password") + let hashedPassword = try BCryptDigest().hash("password") user = testWorld.createUser(password: hashedPassword) let loginData = LoginData(username: user.username, password: "password") let loginResponse = try testWorld.getResponse(to: "/blog/admin/login", method: .POST, body: loginData) @@ -44,26 +44,23 @@ class LoginTests: XCTestCase { XCTAssertNotNil(loginResponse.cookies["steampress-session"]) let sessionCookie = loginResponse.cookies["steampress-session"] - var adminRequest = HTTPRequest(method: .GET, url: URL(string: "/blog/admin")!) + let adminRequest = Request(application: testWorld.context.app, method: .GET, url: URI(path: "/blog/admin"), on: testWorld.context.app.eventLoopGroup.next()) adminRequest.cookies["steampress-session"] = sessionCookie - let wrappedAdminRequest = Request(http: adminRequest, using: testWorld.context.app!) - let adminResponse = try testWorld.getResponse(to: wrappedAdminRequest) + let adminResponse = try testWorld.getResponse(to: adminRequest) XCTAssertEqual(adminResponse.status, .ok) - var logoutRequest = HTTPRequest(method: .POST, url: URL(string: "/blog/admin/logout")!) + let logoutRequest = Request(application: testWorld.context.app, method: .POST, url: URI(path: "/blog/admin/logout"), on: testWorld.context.app.eventLoopGroup.next()) logoutRequest.cookies["steampress-session"] = sessionCookie - let wrappedLogoutRequest = Request(http: logoutRequest, using: testWorld.context.app!) - let logoutResponse = try testWorld.getResponse(to: wrappedLogoutRequest) + let logoutResponse = try testWorld.getResponse(to: logoutRequest) XCTAssertEqual(logoutResponse.status, .seeOther) XCTAssertEqual(logoutResponse.headers[.location].first, "/blog/") - var secondAdminRequest = HTTPRequest(method: .GET, url: URL(string: "/blog/admin")!) + let secondAdminRequest = Request(application: testWorld.context.app, method: .GET, url: URI(path: "/blog/admin"), on: testWorld.context.app.eventLoopGroup.next()) secondAdminRequest.cookies["steampress-session"] = sessionCookie - let wrappedSecondRequest = Request(http: secondAdminRequest, using: testWorld.context.app!) - let loggedOutAdminResponse = try testWorld.getResponse(to: wrappedSecondRequest) + let loggedOutAdminResponse = try testWorld.getResponse(to: secondAdminRequest) XCTAssertEqual(loggedOutAdminResponse.status, .seeOther) XCTAssertEqual(loggedOutAdminResponse.headers[.location].first, "/blog/admin/login/?loginRequired") @@ -269,10 +266,9 @@ class LoginTests: XCTestCase { let loginResponse = try testWorld.getResponse(to: "/blog/admin/login", method: .POST, body: loginData) let cookie = loginResponse.cookies["steampress-session"] - var adminRequest = HTTPRequest(method: .GET, url: URL(string: "/blog/admin")!) + let adminRequest = Request(application: testWorld.context.app, method: .GET, url: URI(path: "/blog/admin"), on: testWorld.context.app.eventLoopGroup.next()) adminRequest.cookies["steampress-session"] = cookie - let wrappedAdminRequest = Request(http: adminRequest, using: testWorld.context.app!) - let response = try testWorld.getResponse(to: wrappedAdminRequest) + let response = try testWorld.getResponse(to: adminRequest) XCTAssertEqual(loginResponse.cookies["steampress-session"]?.expires, response.cookies["steampress-session"]?.expires) } diff --git a/Tests/SteamPressTests/Helpers/TestWorld.swift b/Tests/SteamPressTests/Helpers/TestWorld.swift index 8bd614a6..095b0f96 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld.swift @@ -23,27 +23,28 @@ struct TestWorld { } struct Context { - var app: Application? + var app: Application let repository: InMemoryRepository let blogPresenter: CapturingBlogPresenter let blogAdminPresenter: CapturingAdminPresenter let path: String? } - // To work around Vapor 3 dodgy lifecycle mess - mutating func tryAsHardAsWeCanToShutdownApplication() throws { - struct ApplicationDidNotGoAway: Error { - var description: String - } - weak var weakApp: Application? = context.app - context.app = nil - var tries = 0 - while weakApp != nil && tries < 10 { - Thread.sleep(forTimeInterval: 0.1) - tries += 1 - } - if weakApp != nil { - throw ApplicationDidNotGoAway(description: "application leak: \(weakApp.debugDescription)") - } - } + #warning("Remove") +// // To work around Vapor 3 dodgy lifecycle mess +// mutating func tryAsHardAsWeCanToShutdownApplication() throws { +// struct ApplicationDidNotGoAway: Error { +// var description: String +// } +// weak var weakApp: Application? = context.app +// context.app = nil +// var tries = 0 +// while weakApp != nil && tries < 10 { +// Thread.sleep(forTimeInterval: 0.1) +// tries += 1 +// } +// if weakApp != nil { +// throw ApplicationDidNotGoAway(description: "application leak: \(weakApp.debugDescription)") +// } +// } } From 5c485c8c520abfec95438fd0e6ab831a41fa8b56 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 3 Apr 2020 17:26:13 +0100 Subject: [PATCH 35/70] Migrate some tests to new throwing setup --- Tests/SteamPressTests/BlogTests/PostTests.swift | 6 +++--- Tests/SteamPressTests/BlogTests/SearchTests.swift | 6 +++--- Tests/SteamPressTests/Helpers/TestWorld.swift | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/SteamPressTests/BlogTests/PostTests.swift b/Tests/SteamPressTests/BlogTests/PostTests.swift index 8f213f90..53a9eca2 100644 --- a/Tests/SteamPressTests/BlogTests/PostTests.swift +++ b/Tests/SteamPressTests/BlogTests/PostTests.swift @@ -16,9 +16,9 @@ class PostTests: XCTestCase { // MARK: - Overrides - override func setUp() { - testWorld = try! TestWorld.create() - firstData = try! testWorld.createPost(title: "Test Path", slugUrl: "test-path") + override func setUpWithError() throws { + testWorld = try TestWorld.create() + firstData = try testWorld.createPost(title: "Test Path", slugUrl: "test-path") } override func tearDown() { diff --git a/Tests/SteamPressTests/BlogTests/SearchTests.swift b/Tests/SteamPressTests/BlogTests/SearchTests.swift index 4486058f..13b09ddc 100644 --- a/Tests/SteamPressTests/BlogTests/SearchTests.swift +++ b/Tests/SteamPressTests/BlogTests/SearchTests.swift @@ -15,9 +15,9 @@ class SearchTests: XCTestCase { // MARK: - Overrides - override func setUp() { - testWorld = try! TestWorld.create() - firstData = try! testWorld.createPost(title: "Test Path", slugUrl: "test-path") + override func setUpWithError() throws { + testWorld = try TestWorld.create() + firstData = try testWorld.createPost(title: "Test Path", slugUrl: "test-path") } override func tearDown() { diff --git a/Tests/SteamPressTests/Helpers/TestWorld.swift b/Tests/SteamPressTests/Helpers/TestWorld.swift index 095b0f96..dd54c994 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld.swift @@ -31,8 +31,8 @@ struct TestWorld { } #warning("Remove") -// // To work around Vapor 3 dodgy lifecycle mess -// mutating func tryAsHardAsWeCanToShutdownApplication() throws { + // To work around Vapor 3 dodgy lifecycle mess + mutating func tryAsHardAsWeCanToShutdownApplication() throws { // struct ApplicationDidNotGoAway: Error { // var description: String // } @@ -46,5 +46,5 @@ struct TestWorld { // if weakApp != nil { // throw ApplicationDidNotGoAway(description: "application leak: \(weakApp.debugDescription)") // } -// } + } } From 9fc5e6a87c9a723b88c74cb7bd7b716888f66347 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 3 Apr 2020 17:32:11 +0100 Subject: [PATCH 36/70] Get provider tests working --- Tests/SteamPressTests/ProviderTests.swift | 49 ++++++++++------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/Tests/SteamPressTests/ProviderTests.swift b/Tests/SteamPressTests/ProviderTests.swift index 05d7555a..4490a1b8 100644 --- a/Tests/SteamPressTests/ProviderTests.swift +++ b/Tests/SteamPressTests/ProviderTests.swift @@ -1,42 +1,37 @@ import XCTest -import SteamPress +@testable import SteamPress import Vapor class ProviderTests: XCTestCase { func testUsingProviderSetsCorrectServices() throws { - var services = Services.default() - let steampress = SteamPress.Provider() - try services.register(steampress) - - var middlewareConfig = MiddlewareConfig() - middlewareConfig.use(ErrorMiddleware.self) - middlewareConfig.use(BlogRememberMeMiddleware.self) - middlewareConfig.use(SessionsMiddleware.self) - services.register(middlewareConfig) + let app = Application() + app.lifecycle.use(SteamPress.SteampressLifecyle()) + app.middleware.use(BlogRememberMeMiddleware()) + app.middleware.use(SessionsMiddleware(session: app.sessions.driver)) - services.register([BlogTagRepository.self, BlogPostRepository.self, BlogUserRepository.self]) { _ in - return InMemoryRepository() - } - - let app: Application? = try Application(services: services) + #warning("TODO") +// services.register([BlogTagRepository.self, BlogPostRepository.self, BlogUserRepository.self]) { _ in +// return InMemoryRepository() +// } - let numberGenerator = try app!.make(SteamPressRandomNumberGenerator.self) + let numberGenerator = app.randomNumberGenerators.generator XCTAssertTrue(type(of: numberGenerator) == RealRandomNumberGenerator.self) - let blogPresenter = try app!.make(BlogPresenter.self) + let blogPresenter = app.blogPresenters.blogPresenter XCTAssertTrue(type(of: blogPresenter) == ViewBlogPresenter.self) - let blogAdminPresenter = try app!.make(BlogAdminPresenter.self) + let blogAdminPresenter = app.adminPresenters.adminPresenter XCTAssertTrue(type(of: blogAdminPresenter) == ViewBlogAdminPresenter.self) - // Work around Vapor 3 lifecycle mess - weak var weakApp: Application? = app - weakApp = nil - var tries = 0 - while weakApp != nil && tries < 10 { - Thread.sleep(forTimeInterval: 0.1) - tries += 1 - } - XCTAssertNil(weakApp, "application leak: \(weakApp.debugDescription)") + #warning("Remove") +// // Work around Vapor 3 lifecycle mess +// weak var weakApp: Application? = app +// weakApp = nil +// var tries = 0 +// while weakApp != nil && tries < 10 { +// Thread.sleep(forTimeInterval: 0.1) +// tries += 1 +// } +// XCTAssertNil(weakApp, "application leak: \(weakApp.debugDescription)") } } From 55229688cd7b20e3f19e8d87f2feb0c5f290aea1 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 3 Apr 2020 17:39:42 +0100 Subject: [PATCH 37/70] Get the ReversedPasswordHasher working --- .../Extensions/BCrypt+PasswordHasher.swift | 16 +++++++------- .../Fakes/ReversedPasswordHasher.swift | 22 ++++++++++++------- .../Fakes/StubbedRandomNumberGenerator.swift | 2 +- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift index eb96e9a0..f0bcd7a7 100644 --- a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift +++ b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift @@ -1,38 +1,38 @@ import Vapor import Crypto -protocol PasswordHasher { +public protocol PasswordHasher { func `for`(_ request: Request) -> PasswordHasher func hash(_ plaintext: String) throws -> String } extension BCryptDigest: PasswordHasher { - func hash(_ plaintext: String) throws -> String { + public func hash(_ plaintext: String) throws -> String { return try self.hash(plaintext) } - func `for`(_ request: Request) -> PasswordHasher { + public func `for`(_ request: Request) -> PasswordHasher { return BCryptDigest() } } -protocol SteamPressPasswordVerifier { +public protocol SteamPressPasswordVerifier { func `for`(_ request: Request) -> SteamPressPasswordVerifier func verify(_ plaintext: String, created hash: String) throws -> Bool } extension BCryptDigest: SteamPressPasswordVerifier { - func verify(_ plaintext: String, created hash: String) throws -> Bool { + public func verify(_ plaintext: String, created hash: String) throws -> Bool { return try self.verify(plaintext, created: hash) } - func `for`(_ request: Request) -> SteamPressPasswordVerifier { + public func `for`(_ request: Request) -> SteamPressPasswordVerifier { return BCryptDigest() } } -extension Request { +public extension Request { var passwordHasher: PasswordHasher { self.application.passwordHashers.passwordHasher.for(self) } @@ -42,7 +42,7 @@ extension Request { } } -extension Application { +public extension Application { struct PasswordVerifiers { struct Provider { static var bcrypt: Self { diff --git a/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift b/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift index 40d97a14..54c4338a 100644 --- a/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift +++ b/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift @@ -1,14 +1,20 @@ import Vapor import SteamPress -struct ReversedPasswordHasher: PasswordHasher, PasswordVerifier { - func hash(_ plaintext: LosslessDataConvertible) throws -> String { - return String(String.convertFromData(plaintext.convertToData()).reversed()) +struct ReversedPasswordHasher: PasswordHasher, SteamPressPasswordVerifier { + func `for`(_ request: Request) -> PasswordHasher { + return ReversedPasswordHasher() } - - func verify(_ password: LosslessDataConvertible, created hash: LosslessDataConvertible) throws -> Bool { - let passwordString = String.convertFromData(password.convertToData()) - let passwordHash = String.convertFromData(hash.convertToData()) - return passwordString == String(passwordHash.reversed()) + + func `for`(_ request: Request) -> SteamPressPasswordVerifier { + return ReversedPasswordHasher() + } + + func hash(_ plaintext: String) throws -> String { + return String(plaintext.reversed()) + } + + func verify(_ plaintext: String, created hash: String) throws -> Bool { + return plaintext == String(hash.reversed()) } } diff --git a/Tests/SteamPressTests/Fakes/StubbedRandomNumberGenerator.swift b/Tests/SteamPressTests/Fakes/StubbedRandomNumberGenerator.swift index 18a03431..65de6ff9 100644 --- a/Tests/SteamPressTests/Fakes/StubbedRandomNumberGenerator.swift +++ b/Tests/SteamPressTests/Fakes/StubbedRandomNumberGenerator.swift @@ -3,7 +3,7 @@ import Vapor struct StubbedRandomNumberGenerator: SteamPressRandomNumberGenerator { func `for`(_ request: Request) -> SteamPressRandomNumberGenerator { - return SteamPressRandomNumberGenerator(numberToReturn: self.numberToReturn) + return StubbedRandomNumberGenerator(numberToReturn: self.numberToReturn) } let numberToReturn: Int From ce9682e66a915e0b72fcdef22f1c10955cc4a4b7 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 3 Apr 2020 17:42:51 +0100 Subject: [PATCH 38/70] Get the TestWorld compiling --- Tests/SteamPressTests/Helpers/TestWorld.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Tests/SteamPressTests/Helpers/TestWorld.swift b/Tests/SteamPressTests/Helpers/TestWorld.swift index dd54c994..393be51c 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld.swift @@ -4,11 +4,12 @@ import Vapor struct TestWorld { static func create(path: String? = nil, postsPerPage: Int = 10, feedInformation: FeedInformation = FeedInformation(), enableAuthorPages: Bool = true, enableTagPages: Bool = true, passwordHasherToUse: PasswordHasherChoice = .plaintext, randomNumberGenerator: StubbedRandomNumberGenerator = StubbedRandomNumberGenerator(numberToReturn: 666)) throws -> TestWorld { - let repository = InMemoryRepository() - let blogPresenter = CapturingBlogPresenter() - let blogAdminPresenter = CapturingAdminPresenter() + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + let repository = InMemoryRepository(eventLoop: eventLoopGroup.next()) + let blogPresenter = CapturingBlogPresenter(eventLoop: eventLoopGroup.next()) + let blogAdminPresenter = CapturingAdminPresenter(eventLoop: eventLoopGroup.next()) let application = try TestWorld.getSteamPressApp(repository: repository, path: path, postsPerPage: postsPerPage, feedInformation: feedInformation, blogPresenter: blogPresenter, adminPresenter: blogAdminPresenter, enableAuthorPages: enableAuthorPages, enableTagPages: enableTagPages, passwordHasherToUse: passwordHasherToUse, randomNumberGenerator: randomNumberGenerator) - let context = Context(app: application, repository: repository, blogPresenter: blogPresenter, blogAdminPresenter: blogAdminPresenter, path: path) + let context = Context(app: application, repository: repository, blogPresenter: blogPresenter, blogAdminPresenter: blogAdminPresenter, path: path, eventLoopGroup: eventLoopGroup) unsetenv("BLOG_GOOGLE_ANALYTICS_IDENTIFIER") unsetenv("BLOG_SITE_TWITTER_HANDLE") unsetenv("BLOG_DISQUS_NAME") @@ -28,11 +29,13 @@ struct TestWorld { let blogPresenter: CapturingBlogPresenter let blogAdminPresenter: CapturingAdminPresenter let path: String? + let eventLoopGroup: EventLoopGroup } - #warning("Remove") + #warning("Remove or rename") // To work around Vapor 3 dodgy lifecycle mess mutating func tryAsHardAsWeCanToShutdownApplication() throws { + try context.eventLoopGroup.syncShutdownGracefully() // struct ApplicationDidNotGoAway: Error { // var description: String // } From 07317341fdaf503cf77b68342f0f50c88232f41b Mon Sep 17 00:00:00 2001 From: Changwei Tu Date: Sat, 4 Apr 2020 17:29:34 +0800 Subject: [PATCH 39/70] modify and register CapturingViewRenderer to test the Context which will render to Leaf page --- Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift | 2 +- .../SteamPressTests/ViewTests/BlogAdminPresenterTests.swift | 4 +++- Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift | 4 +++- Tests/SteamPressTests/ViewTests/BlogViewTests.swift | 5 ++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift b/Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift index 34ab230f..38eed9a0 100644 --- a/Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift +++ b/Tests/SteamPressTests/Fakes/CapturingViewRenderer.swift @@ -9,7 +9,7 @@ class CapturingViewRenderer: ViewRenderer { } func `for`(_ request: Request) -> ViewRenderer { - return CapturingViewRenderer(eventLoop: request.eventLoop) + return self } private(set) var capturedContext: Encodable? diff --git a/Tests/SteamPressTests/ViewTests/BlogAdminPresenterTests.swift b/Tests/SteamPressTests/ViewTests/BlogAdminPresenterTests.swift index 8090cfcb..6be8db1a 100644 --- a/Tests/SteamPressTests/ViewTests/BlogAdminPresenterTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogAdminPresenterTests.swift @@ -21,17 +21,19 @@ class BlogAdminPresenterTests: XCTestCase { private static let siteTwitterHandle = "brokenhandsio" private static let disqusName = "steampress" private static let googleAnalyticsIdentifier = "UA-12345678-1" - + let app = Application(.testing) // MARK: - Overrides override func setUp() { eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) + app.views.use {_ in self.viewRenderer } presenter = ViewBlogAdminPresenter(pathCreator: BlogPathCreator(blogPath: "blog"), viewRenderer: viewRenderer, eventLoopGroup: eventLoopGroup, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) } override func tearDownWithError() throws { try eventLoopGroup.syncShutdownGracefully() + app.shutdown() } // MARK: - Tests diff --git a/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift b/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift index cef59a96..51bb79e9 100644 --- a/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift @@ -22,18 +22,20 @@ class BlogPresenterTests: XCTestCase { private static let siteTwitterHandle = "brokenhandsio" private static let disqusName = "steampress" private static let googleAnalyticsIdentifier = "UA-12345678-1" - + let app = Application(.testing) // MARK: - Overrides override func setUp() { eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) + app.views.use {_ in self.viewRenderer } presenter = ViewBlogPresenter(viewRenderer: viewRenderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: eventLoopGroup) testTag = BlogTag(id: 1, name: "Tattoine") } override func tearDownWithError() throws { try eventLoopGroup.syncShutdownGracefully() + app.shutdown() } // MARK: - Tests diff --git a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift index 9e21016b..a14066a2 100644 --- a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift @@ -13,12 +13,14 @@ class BlogViewTests: XCTestCase { var pageInformation: BlogGlobalPageInformation! var websiteURL: URL! var currentPageURL: URL! - + let app = Application(.testing) + // MARK: - Overrides override func setUp() { eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) + app.views.use {_ in self.viewRenderer } presenter = ViewBlogPresenter(viewRenderer: viewRenderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: eventLoopGroup) author = TestDataBuilder.anyUser() author.userID = 1 @@ -32,6 +34,7 @@ class BlogViewTests: XCTestCase { override func tearDownWithError() throws { try eventLoopGroup.syncShutdownGracefully() + app.shutdown() } // MARK: - Tests From 365e2bc4ed29114fe2e57abf5aeb8b6e224e4348 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Sun, 5 Apr 2020 07:16:42 +0100 Subject: [PATCH 40/70] Port the password stuff over in tests --- .../Extensions/BCrypt+PasswordHasher.swift | 16 +++---- .../Middleware/BlogRememberMeMiddleware.swift | 2 + .../Repositories/SteamPressRepository.swift | 8 ++-- .../SteamPressRandomNumberGenerator.swift | 8 ++-- .../Fakes/PlaintextHasher.swift | 45 ++++++++++++++++--- .../Fakes/ReversedPasswordHasher.swift | 28 ++++++++++++ .../Helpers/TestWorld+Application.swift | 44 +++++++----------- .../Helpers/TestWorld+Responses.swift | 9 ++-- Tests/SteamPressTests/Helpers/TestWorld.swift | 3 +- Tests/SteamPressTests/ProviderTests.swift | 11 +---- .../ViewTests/BlogAdminPresenterTests.swift | 3 -- .../ViewTests/BlogPresenterTests.swift | 3 -- .../ViewTests/BlogViewTests.swift | 3 -- 13 files changed, 109 insertions(+), 74 deletions(-) diff --git a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift index f0bcd7a7..84cd87ad 100644 --- a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift +++ b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift @@ -44,8 +44,8 @@ public extension Request { public extension Application { struct PasswordVerifiers { - struct Provider { - static var bcrypt: Self { + public struct Provider { + public static var bcrypt: Self { .init { $0.passwordVerifiers.use { $0.passwordVerifiers.bcrypt } } @@ -53,7 +53,7 @@ public extension Application { let run: (Application) -> () - init(_ run: @escaping (Application) -> ()) { + public init(_ run: @escaping (Application) -> ()) { self.run = run } } @@ -80,7 +80,7 @@ public extension Application { return makeVerifier(self.application) } - func use(_ provider: Provider) { + public func use(_ provider: Provider) { provider.run(self.application) } @@ -106,8 +106,8 @@ public extension Application { } struct PasswordHashers { - struct Provider { - static var bcrypt: Self { + public struct Provider { + public static var bcrypt: Self { .init { $0.passwordHashers.use { $0.passwordHashers.bcrypt } } @@ -115,7 +115,7 @@ public extension Application { let run: (Application) -> () - init(_ run: @escaping (Application) -> ()) { + public init(_ run: @escaping (Application) -> ()) { self.run = run } } @@ -142,7 +142,7 @@ public extension Application { return makeHasher(self.application) } - func use(_ provider: Provider) { + public func use(_ provider: Provider) { provider.run(self.application) } diff --git a/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift b/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift index e3715c78..61e20011 100644 --- a/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift +++ b/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift @@ -2,6 +2,8 @@ import Vapor public struct BlogRememberMeMiddleware: Middleware { + public init() + public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { return next.respond(to: request).map { response in if let rememberMe = request.session.data["SteamPressRememberMe"], rememberMe == "YES" { diff --git a/Sources/SteamPress/Repositories/SteamPressRepository.swift b/Sources/SteamPress/Repositories/SteamPressRepository.swift index df88f35d..f08a4e0b 100644 --- a/Sources/SteamPress/Repositories/SteamPressRepository.swift +++ b/Sources/SteamPress/Repositories/SteamPressRepository.swift @@ -59,7 +59,7 @@ public extension Request { } } -private extension Application { +public extension Application { private struct BlogUserRepositoryKey: StorageKey { typealias Value = BlogUserRepositoryFactory } @@ -97,21 +97,21 @@ private extension Application { } } -private struct BlogUserRepositoryFactory { +public struct BlogUserRepositoryFactory { var makeRepository: ((Request) -> BlogUserRepository)? mutating func use(_ makeRepository: @escaping (Request) -> BlogUserRepository) { self.makeRepository = makeRepository } } -private struct BlogPostRepositoryFactory { +public struct BlogPostRepositoryFactory { var makeRepository: ((Request) -> BlogPostRepository)? mutating func use(_ makeRepository: @escaping (Request) -> BlogPostRepository) { self.makeRepository = makeRepository } } -private struct BlogTagRepositoryFactory { +public struct BlogTagRepositoryFactory { var makeRepository: ((Request) -> BlogTagRepository)? mutating func use(_ makeRepository: @escaping (Request) -> BlogTagRepository) { self.makeRepository = makeRepository diff --git a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift index eedf7e50..4accf512 100644 --- a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift +++ b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift @@ -17,9 +17,9 @@ public extension Request { } } -extension Application { +public extension Application { struct RandomNumberGenerators { - struct Provider { + public struct Provider { static var real: Self { .init { $0.randomNumberGenerators.use { $0.randomNumberGenerators.real } @@ -55,11 +55,11 @@ extension Application { return makeGenerator(self.application) } - func use(_ provider: Provider) { + public func use(_ provider: Provider) { provider.run(self.application) } - func use(_ makeGenerator: @escaping (Application) -> (SteamPressRandomNumberGenerator)) { + public func use(_ makeGenerator: @escaping (Application) -> (SteamPressRandomNumberGenerator)) { self.storage.makeGenerator = makeGenerator } diff --git a/Tests/SteamPressTests/Fakes/PlaintextHasher.swift b/Tests/SteamPressTests/Fakes/PlaintextHasher.swift index ead4ed0f..7e293868 100644 --- a/Tests/SteamPressTests/Fakes/PlaintextHasher.swift +++ b/Tests/SteamPressTests/Fakes/PlaintextHasher.swift @@ -1,8 +1,43 @@ import Vapor import SteamPress -//struct PlaintextHasher: PasswordHasher { -// func hash(_ plaintext: LosslessDataConvertible) throws -> String { -// return String.convertFromData(plaintext.convertToData()) -// } -//} +struct PlaintextHasher: PasswordHasher { + + func hash(_ plaintext: String) throws -> String { + return plaintext + } + + func `for`(_ request: Request) -> PasswordHasher { + return PlaintextHasher() + } +} + +extension Application.PasswordHashers.Provider { + public static var plaintext: Self { + .init { + $0.passwordHashers.use { $0.passwordHashers.plaintext } + } + } +} + +extension Application.PasswordHashers { + var plaintext: PlaintextHasher { + return .init() + } +} + +extension PlaintextVerifier: SteamPressPasswordVerifier {} + +extension Application.PasswordVerifiers { + var plaintext: PlaintextVerifier { + return .init() + } +} + +extension Application.PasswordVerifiers.Provider { + public static var plaintext: Self { + .init { + $0.passwordVerifiers.use { $0.passwordVerifiers.plaintext } + } + } +} diff --git a/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift b/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift index 54c4338a..587dafa2 100644 --- a/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift +++ b/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift @@ -18,3 +18,31 @@ struct ReversedPasswordHasher: PasswordHasher, SteamPressPasswordVerifier { return plaintext == String(hash.reversed()) } } + +extension Application.PasswordHashers.Provider { + public static var reversed: Self { + .init { + $0.passwordHashers.use { $0.passwordHashers.reversed } + } + } +} + +extension Application.PasswordHashers { + var reversed: ReversedPasswordHasher { + return .init() + } +} + +extension Application.PasswordVerifiers { + var reversed: ReversedPasswordHasher { + return .init() + } +} + +extension Application.PasswordVerifiers.Provider { + public static var reversed: Self { + .init { + $0.passwordVerifiers.use { $0.passwordVerifiers.reversed } + } + } +} diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift index 381b76cb..bb103c97 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift @@ -2,7 +2,8 @@ import SteamPress import Vapor extension TestWorld { - static func getSteamPressApp(repository: InMemoryRepository, + static func getSteamPressApp(eventLoopGroup: EventLoopGroup, + repository: InMemoryRepository, path: String?, postsPerPage: Int, feedInformation: FeedInformation, @@ -12,22 +13,23 @@ extension TestWorld { enableTagPages: Bool, passwordHasherToUse: PasswordHasherChoice, randomNumberGenerator: StubbedRandomNumberGenerator) throws -> Application { - var services = Services.default() - let steampress = SteamPress.Provider( + + let application = Application(.testing, .shared(eventLoopGroup)) + + let steampress = SteamPress.SteampressLifecyle( blogPath: path, feedInformation: feedInformation, postsPerPage: postsPerPage, enableAuthorPages: enableAuthorPages, enableTagPages: enableTagPages) - try services.register(steampress) + application.lifecycle.use(steampress) + services.register([BlogTagRepository.self, BlogPostRepository.self, BlogUserRepository.self]) { _ in return repository } - services.register(SteamPressRandomNumberGenerator.self) { _ in - return randomNumberGenerator - } + application.randomNumberGenerators.use { _ in randomNumberGenerator } services.register(BlogPresenter.self) { _ in return blogPresenter @@ -37,34 +39,22 @@ extension TestWorld { return adminPresenter } - var middlewareConfig = MiddlewareConfig() - middlewareConfig.use(ErrorMiddleware.self) - middlewareConfig.use(BlogRememberMeMiddleware.self) - middlewareConfig.use(SessionsMiddleware.self) - services.register(middlewareConfig) - - var config = Config.default() + application.middleware.use(BlogRememberMeMiddleware()) + application.middleware.use(SessionsMiddleware(session: application.sessions.driver)) config.prefer(CapturingBlogPresenter.self, for: BlogPresenter.self) config.prefer(CapturingAdminPresenter.self, for: BlogAdminPresenter.self) - config.prefer(StubbedRandomNumberGenerator.self, for: SteamPressRandomNumberGenerator.self) switch passwordHasherToUse { case .real: - config.prefer(BCryptDigest.self, for: PasswordVerifier.self) - config.prefer(BCryptDigest.self, for: PasswordHasher.self) + application.passwordHashers.use(.bcrypt) + application.passwordVerifiers.use(.bcrypt) case .plaintext: - services.register(PasswordHasher.self) { _ in - return PlaintextHasher() - } - config.prefer(PlaintextVerifier.self, for: PasswordVerifier.self) - config.prefer(PlaintextHasher.self, for: PasswordHasher.self) + application.passwordHashers.use(.plaintext) + application.passwordVerifiers.use(.plaintext) case .reversed: - services.register([PasswordHasher.self, PasswordVerifier.self]) { _ in - return ReversedPasswordHasher() - } - config.prefer(ReversedPasswordHasher.self, for: PasswordVerifier.self) - config.prefer(ReversedPasswordHasher.self, for: PasswordHasher.self) + application.passwordVerifiers.use(.reversed) + application.passwordHashers.use(.reversed) } return try Application(config: config, services: services) diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Responses.swift b/Tests/SteamPressTests/Helpers/TestWorld+Responses.swift index 29c0ed46..16aeb4ec 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Responses.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Responses.swift @@ -4,7 +4,7 @@ import Vapor extension TestWorld { func getResponse(to path: String, method: HTTPMethod = .GET, headers: HTTPHeaders = .init(), decodeTo type: T.Type) throws -> T where T: Content { let response = try getResponse(to: path, method: method, headers: headers) - return try response.content.decode(type).wait() + return try response.content.decode(type) } func getResponseString(to path: String, headers: HTTPHeaders = .init()) throws -> String { @@ -33,7 +33,7 @@ extension TestWorld { return Request(http: request, using: app) } - func setLoginCookie(for user: BlogUser?, password: String? = nil) throws -> HTTPCookieValue? { + func setLoginCookie(for user: BlogUser?, password: String? = nil) throws -> HTTPSetCookie? { if let user = user { let loginData = LoginData(username: user.username, password: password ?? user.password) var loginPath = "/admin/login" @@ -49,10 +49,7 @@ extension TestWorld { } func getResponse(to request: Request) throws -> Response { - guard let app = context.app else { - fatalError("App has already been deinitiliased") - } - let responder = try app.make(Responder.self) + let responder = try context.app.make(Responder.self) return try responder.respond(to: request).wait() } } diff --git a/Tests/SteamPressTests/Helpers/TestWorld.swift b/Tests/SteamPressTests/Helpers/TestWorld.swift index 393be51c..f0d5346e 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld.swift @@ -8,7 +8,7 @@ struct TestWorld { let repository = InMemoryRepository(eventLoop: eventLoopGroup.next()) let blogPresenter = CapturingBlogPresenter(eventLoop: eventLoopGroup.next()) let blogAdminPresenter = CapturingAdminPresenter(eventLoop: eventLoopGroup.next()) - let application = try TestWorld.getSteamPressApp(repository: repository, path: path, postsPerPage: postsPerPage, feedInformation: feedInformation, blogPresenter: blogPresenter, adminPresenter: blogAdminPresenter, enableAuthorPages: enableAuthorPages, enableTagPages: enableTagPages, passwordHasherToUse: passwordHasherToUse, randomNumberGenerator: randomNumberGenerator) + let application = try TestWorld.getSteamPressApp(eventLoopGroup: eventLoopGroup, repository: repository, path: path, postsPerPage: postsPerPage, feedInformation: feedInformation, blogPresenter: blogPresenter, adminPresenter: blogAdminPresenter, enableAuthorPages: enableAuthorPages, enableTagPages: enableTagPages, passwordHasherToUse: passwordHasherToUse, randomNumberGenerator: randomNumberGenerator) let context = Context(app: application, repository: repository, blogPresenter: blogPresenter, blogAdminPresenter: blogAdminPresenter, path: path, eventLoopGroup: eventLoopGroup) unsetenv("BLOG_GOOGLE_ANALYTICS_IDENTIFIER") unsetenv("BLOG_SITE_TWITTER_HANDLE") @@ -35,6 +35,7 @@ struct TestWorld { #warning("Remove or rename") // To work around Vapor 3 dodgy lifecycle mess mutating func tryAsHardAsWeCanToShutdownApplication() throws { + context.app.shutdown() try context.eventLoopGroup.syncShutdownGracefully() // struct ApplicationDidNotGoAway: Error { // var description: String diff --git a/Tests/SteamPressTests/ProviderTests.swift b/Tests/SteamPressTests/ProviderTests.swift index 4490a1b8..4bb84941 100644 --- a/Tests/SteamPressTests/ProviderTests.swift +++ b/Tests/SteamPressTests/ProviderTests.swift @@ -23,15 +23,6 @@ class ProviderTests: XCTestCase { let blogAdminPresenter = app.adminPresenters.adminPresenter XCTAssertTrue(type(of: blogAdminPresenter) == ViewBlogAdminPresenter.self) - #warning("Remove") -// // Work around Vapor 3 lifecycle mess -// weak var weakApp: Application? = app -// weakApp = nil -// var tries = 0 -// while weakApp != nil && tries < 10 { -// Thread.sleep(forTimeInterval: 0.1) -// tries += 1 -// } -// XCTAssertNil(weakApp, "application leak: \(weakApp.debugDescription)") + app.shutdown() } } diff --git a/Tests/SteamPressTests/ViewTests/BlogAdminPresenterTests.swift b/Tests/SteamPressTests/ViewTests/BlogAdminPresenterTests.swift index 6be8db1a..8b1c54e0 100644 --- a/Tests/SteamPressTests/ViewTests/BlogAdminPresenterTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogAdminPresenterTests.swift @@ -21,19 +21,16 @@ class BlogAdminPresenterTests: XCTestCase { private static let siteTwitterHandle = "brokenhandsio" private static let disqusName = "steampress" private static let googleAnalyticsIdentifier = "UA-12345678-1" - let app = Application(.testing) // MARK: - Overrides override func setUp() { eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) - app.views.use {_ in self.viewRenderer } presenter = ViewBlogAdminPresenter(pathCreator: BlogPathCreator(blogPath: "blog"), viewRenderer: viewRenderer, eventLoopGroup: eventLoopGroup, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) } override func tearDownWithError() throws { try eventLoopGroup.syncShutdownGracefully() - app.shutdown() } // MARK: - Tests diff --git a/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift b/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift index 51bb79e9..29462b43 100644 --- a/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogPresenterTests.swift @@ -22,20 +22,17 @@ class BlogPresenterTests: XCTestCase { private static let siteTwitterHandle = "brokenhandsio" private static let disqusName = "steampress" private static let googleAnalyticsIdentifier = "UA-12345678-1" - let app = Application(.testing) // MARK: - Overrides override func setUp() { eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) - app.views.use {_ in self.viewRenderer } presenter = ViewBlogPresenter(viewRenderer: viewRenderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: eventLoopGroup) testTag = BlogTag(id: 1, name: "Tattoine") } override func tearDownWithError() throws { try eventLoopGroup.syncShutdownGracefully() - app.shutdown() } // MARK: - Tests diff --git a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift index a14066a2..e9f46f3d 100644 --- a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift @@ -13,7 +13,6 @@ class BlogViewTests: XCTestCase { var pageInformation: BlogGlobalPageInformation! var websiteURL: URL! var currentPageURL: URL! - let app = Application(.testing) // MARK: - Overrides @@ -22,7 +21,6 @@ class BlogViewTests: XCTestCase { viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) app.views.use {_ in self.viewRenderer } presenter = ViewBlogPresenter(viewRenderer: viewRenderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: eventLoopGroup) - author = TestDataBuilder.anyUser() author.userID = 1 let createdDate = Date(timeIntervalSince1970: 1584714638) let lastEditedDate = Date(timeIntervalSince1970: 1584981458) @@ -34,7 +32,6 @@ class BlogViewTests: XCTestCase { override func tearDownWithError() throws { try eventLoopGroup.syncShutdownGracefully() - app.shutdown() } // MARK: - Tests From 55a99731e9d9d22e88fb62ff9a7e26dbc30cea1e Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Sun, 5 Apr 2020 07:26:48 +0100 Subject: [PATCH 41/70] Get password and presenter configuration compiling --- .../Extensions/BCrypt+PasswordHasher.swift | 4 ++-- .../Middleware/BlogRememberMeMiddleware.swift | 2 +- .../SteamPress/Presenters/BlogAdminPresenter.swift | 6 +++--- Sources/SteamPress/Presenters/BlogPresenter.swift | 12 ++++++------ Tests/SteamPressTests/Fakes/PlaintextHasher.swift | 10 +++++++++- .../Fakes/Presenters/CapturingAdminPresenter.swift | 14 ++++++++++++++ .../Fakes/Presenters/CapturingBlogPresenter.swift | 14 ++++++++++++++ .../Helpers/TestWorld+Application.swift | 14 +++----------- .../SteamPressTests/ViewTests/BlogViewTests.swift | 1 - 9 files changed, 52 insertions(+), 25 deletions(-) diff --git a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift index 84cd87ad..b5b84767 100644 --- a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift +++ b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift @@ -84,7 +84,7 @@ public extension Application { provider.run(self.application) } - func use(_ makeVerifier: @escaping (Application) -> (SteamPressPasswordVerifier)) { + public func use(_ makeVerifier: @escaping (Application) -> (SteamPressPasswordVerifier)) { self.storage.makeVerifier = makeVerifier } @@ -146,7 +146,7 @@ public extension Application { provider.run(self.application) } - func use(_ makeHasher: @escaping (Application) -> (PasswordHasher)) { + public func use(_ makeHasher: @escaping (Application) -> (PasswordHasher)) { self.storage.makeHasher = makeHasher } diff --git a/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift b/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift index 61e20011..bcfb7d6d 100644 --- a/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift +++ b/Sources/SteamPress/Middleware/BlogRememberMeMiddleware.swift @@ -2,7 +2,7 @@ import Vapor public struct BlogRememberMeMiddleware: Middleware { - public init() + public init() {} public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { return next.respond(to: request).map { response in diff --git a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift index b603f03d..6458ddc8 100644 --- a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift @@ -20,9 +20,9 @@ extension Request { } } -extension Application { +public extension Application { struct BlogAdminPresenters { - struct Provider { + public struct Provider { static var view: Self { .init { $0.adminPresenters.use { $0.adminPresenters.view } @@ -61,7 +61,7 @@ extension Application { return makePresenter(self.application) } - func use(_ provider: Provider) { + public func use(_ provider: Provider) { provider.run(self.application) } diff --git a/Sources/SteamPress/Presenters/BlogPresenter.swift b/Sources/SteamPress/Presenters/BlogPresenter.swift index d88eaf8e..ffd0d3cc 100644 --- a/Sources/SteamPress/Presenters/BlogPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogPresenter.swift @@ -24,9 +24,9 @@ public extension Request { } } -extension Application { +public extension Application { struct BlogPresenters { - struct Provider { + public struct Provider { static var view: Self { .init { $0.blogPresenters.use { $0.blogPresenters.view } @@ -35,7 +35,7 @@ extension Application { let run: (Application) -> () - init(_ run: @escaping (Application) -> ()) { + public init(_ run: @escaping (Application) -> ()) { self.run = run } } @@ -49,7 +49,7 @@ extension Application { typealias Value = Storage } - let application: Application + public let application: Application var view: ViewBlogPresenter { return .init(viewRenderer: self.application.views.renderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: self.application.eventLoopGroup) @@ -62,11 +62,11 @@ extension Application { return makePresenter(self.application) } - func use(_ provider: Provider) { + public func use(_ provider: Provider) { provider.run(self.application) } - func use(_ makePresenter: @escaping (Application) -> (BlogPresenter)) { + public func use(_ makePresenter: @escaping (Application) -> (BlogPresenter)) { self.storage.makePresenter = makePresenter } diff --git a/Tests/SteamPressTests/Fakes/PlaintextHasher.swift b/Tests/SteamPressTests/Fakes/PlaintextHasher.swift index 7e293868..09d06cac 100644 --- a/Tests/SteamPressTests/Fakes/PlaintextHasher.swift +++ b/Tests/SteamPressTests/Fakes/PlaintextHasher.swift @@ -26,7 +26,15 @@ extension Application.PasswordHashers { } } -extension PlaintextVerifier: SteamPressPasswordVerifier {} +extension PlaintextVerifier: SteamPressPasswordVerifier { + public func `for`(_ request: Request) -> SteamPressPasswordVerifier { + return PlaintextVerifier() + } + + public func verify(_ plaintext: String, created hash: String) throws -> Bool { + return plaintext == hash + } +} extension Application.PasswordVerifiers { var plaintext: PlaintextVerifier { diff --git a/Tests/SteamPressTests/Fakes/Presenters/CapturingAdminPresenter.swift b/Tests/SteamPressTests/Fakes/Presenters/CapturingAdminPresenter.swift index 7164601c..5890dbaa 100644 --- a/Tests/SteamPressTests/Fakes/Presenters/CapturingAdminPresenter.swift +++ b/Tests/SteamPressTests/Fakes/Presenters/CapturingAdminPresenter.swift @@ -95,3 +95,17 @@ class CapturingAdminPresenter: BlogAdminPresenter { return TestDataBuilder.createFutureView(on: eventLoop) } } + +extension Application.BlogAdminPresenters { + var capturing: CapturingAdminPresenter { + return .init(eventLoop: self.application.eventLoopGroup.next()) + } +} + +extension Application.BlogAdminPresenters.Provider { + static var capturing: Self { + .init { + $0.adminPresenters.use { $0.adminPresenters.capturing } + } + } +} diff --git a/Tests/SteamPressTests/Fakes/Presenters/CapturingBlogPresenter.swift b/Tests/SteamPressTests/Fakes/Presenters/CapturingBlogPresenter.swift index d1242cdd..b618d4a3 100644 --- a/Tests/SteamPressTests/Fakes/Presenters/CapturingBlogPresenter.swift +++ b/Tests/SteamPressTests/Fakes/Presenters/CapturingBlogPresenter.swift @@ -131,3 +131,17 @@ class CapturingBlogPresenter: BlogPresenter { return TestDataBuilder.createFutureView(on: eventLoop) } } + +extension Application.BlogPresenters { + var capturing: CapturingBlogPresenter { + return .init(eventLoop: self.application.eventLoopGroup.next()) + } +} + +extension Application.BlogPresenters.Provider { + static var capturing: Self { + .init { + $0.blogPresenters.use { $0.blogPresenters.capturing } + } + } +} diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift index bb103c97..e23e229c 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift @@ -30,20 +30,12 @@ extension TestWorld { } application.randomNumberGenerators.use { _ in randomNumberGenerator } - - services.register(BlogPresenter.self) { _ in - return blogPresenter - } - - services.register(BlogAdminPresenter.self) { _ in - return adminPresenter - } application.middleware.use(BlogRememberMeMiddleware()) application.middleware.use(SessionsMiddleware(session: application.sessions.driver)) - config.prefer(CapturingBlogPresenter.self, for: BlogPresenter.self) - config.prefer(CapturingAdminPresenter.self, for: BlogAdminPresenter.self) + application.blogPresenters.use(.capturing) + application.adminPresenters.use(.capturing) switch passwordHasherToUse { case .real: @@ -57,6 +49,6 @@ extension TestWorld { application.passwordHashers.use(.reversed) } - return try Application(config: config, services: services) + return Application(.testing, .shared(eventLoopGroup)) } } diff --git a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift index e9f46f3d..ec3477fc 100644 --- a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift @@ -19,7 +19,6 @@ class BlogViewTests: XCTestCase { override func setUp() { eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) - app.views.use {_ in self.viewRenderer } presenter = ViewBlogPresenter(viewRenderer: viewRenderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: eventLoopGroup) author.userID = 1 let createdDate = Date(timeIntervalSince1970: 1584714638) From 5513566a614396d5c8c909edc529591e06fd63b5 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Sun, 5 Apr 2020 07:43:54 +0100 Subject: [PATCH 42/70] Get repository configuration working in test --- .../Repositories/SteamPressRepository.swift | 121 ++++++++++-------- .../Helpers/InMemoryRepository.swift | 12 ++ .../Helpers/TestWorld+Application.swift | 3 +- Tests/SteamPressTests/ProviderTests.swift | 7 +- 4 files changed, 84 insertions(+), 59 deletions(-) diff --git a/Sources/SteamPress/Repositories/SteamPressRepository.swift b/Sources/SteamPress/Repositories/SteamPressRepository.swift index f08a4e0b..28be8905 100644 --- a/Sources/SteamPress/Repositories/SteamPressRepository.swift +++ b/Sources/SteamPress/Repositories/SteamPressRepository.swift @@ -1,11 +1,12 @@ import Vapor public protocol SteamPressRepository { -// associatedtype ModelType -// func get(_ id: Int, on eventLoop: EventLoop) -> EventLoopFuture + // associatedtype ModelType + // func get(_ id: Int, on eventLoop: EventLoop) -> EventLoopFuture } public protocol BlogTagRepository: SteamPressRepository { + func `for`(_ request: Request) -> BlogTagRepository func getAllTags() -> EventLoopFuture<[BlogTag]> func getAllTagsWithPostCount() -> EventLoopFuture<[(BlogTag, Int)]> func getTags(for post: BlogPost) -> EventLoopFuture<[BlogTag]> @@ -20,6 +21,7 @@ public protocol BlogTagRepository: SteamPressRepository { } public protocol BlogPostRepository: SteamPressRepository { + func `for`(_ request: Request) -> BlogPostRepository func getAllPostsSortedByPublishDate(includeDrafts: Bool) -> EventLoopFuture<[BlogPost]> func getAllPostsCount(includeDrafts: Bool) -> EventLoopFuture func getAllPostsSortedByPublishDate(includeDrafts: Bool, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> @@ -36,6 +38,7 @@ public protocol BlogPostRepository: SteamPressRepository { } public protocol BlogUserRepository: SteamPressRepository { + func `for`(_ request: Request) -> BlogUserRepository func getAllUsers() -> EventLoopFuture<[BlogUser]> func getAllUsersWithPostCount() -> EventLoopFuture<[(BlogUser, Int)]> func getUser(id: Int) -> EventLoopFuture @@ -47,73 +50,85 @@ public protocol BlogUserRepository: SteamPressRepository { public extension Request { var blogUserRepository: BlogUserRepository { - self.application.blogUserRepositoryFactory.makeRepository!(self) + self.application.blogRepositories.userRepository.for(self) } var blogPostRepository: BlogPostRepository { - self.application.blogPostRepositoryFactory.makeRepository!(self) + self.application.blogRepositories.postRepository.for(self) } var blogTagRepository: BlogTagRepository { - self.application.blogTagRepositoryFactory.makeRepository!(self) + self.application.blogRepositories.tagRepository.for(self) } } public extension Application { - private struct BlogUserRepositoryKey: StorageKey { - typealias Value = BlogUserRepositoryFactory - } - var blogUserRepositoryFactory: BlogUserRepositoryFactory { - get { - self.storage[BlogUserRepositoryKey.self] ?? .init() + struct BlogRepositories { + public struct Provider { + let run: (Application) -> () + + public init(_ run: @escaping (Application) -> ()) { + self.run = run + } } - set { - self.storage[BlogUserRepositoryKey.self] = newValue + + final class Storage { + var makePostRepository: ((Application) -> BlogPostRepository)? + var makeTagRepository: ((Application) -> BlogTagRepository)? + var makeUserRepository: ((Application) -> BlogUserRepository)? + init() { } } - } - - private struct BlogPostRepositoryKey: StorageKey { - typealias Value = BlogPostRepositoryFactory - } - var blogPostRepositoryFactory: BlogPostRepositoryFactory { - get { - self.storage[BlogPostRepositoryKey.self] ?? .init() + + struct Key: StorageKey { + typealias Value = Storage } - set { - self.storage[BlogPostRepositoryKey.self] = newValue + + let application: Application + + var userRepository: BlogUserRepository { + guard let makeRepository = self.storage.makeUserRepository else { + fatalError("No user repository configured. Configure with app.blogRepositories.use(...)") + } + return makeRepository(self.application) } - } - - private struct BlogTagRepositoryKey: StorageKey { - typealias Value = BlogTagRepositoryFactory - } - var blogTagRepositoryFactory: BlogTagRepositoryFactory { - get { - self.storage[BlogTagRepositoryKey.self] ?? .init() + + var postRepository: BlogPostRepository { + guard let makeRepository = self.storage.makePostRepository else { + fatalError("No post repository configured. Configure with app.blogRepositories.use(...)") + } + return makeRepository(self.application) } - set { - self.storage[BlogTagRepositoryKey.self] = newValue + + var tagRepository: BlogTagRepository { + guard let makeRepository = self.storage.makeTagRepository else { + fatalError("No tag repository configured. Configure with app.blogRepositories.use(...)") + } + return makeRepository(self.application) + } + + public func use(_ provider: Provider) { + provider.run(self.application) + } + + public func use(_ makeRespository: @escaping (Application) -> (BlogUserRepository & BlogTagRepository & BlogPostRepository)) { + self.storage.makeUserRepository = makeRespository + self.storage.makeTagRepository = makeRespository + self.storage.makePostRepository = makeRespository + } + + func initialize() { + self.application.storage[Key.self] = .init() + } + + private var storage: Storage { + guard let storage = self.application.storage[Key.self] else { + fatalError("Repositoroes not configured. Configure with app.blogRepositories.initialize()") + } + return storage } } -} - -public struct BlogUserRepositoryFactory { - var makeRepository: ((Request) -> BlogUserRepository)? - mutating func use(_ makeRepository: @escaping (Request) -> BlogUserRepository) { - self.makeRepository = makeRepository - } -} - -public struct BlogPostRepositoryFactory { - var makeRepository: ((Request) -> BlogPostRepository)? - mutating func use(_ makeRepository: @escaping (Request) -> BlogPostRepository) { - self.makeRepository = makeRepository - } -} - -public struct BlogTagRepositoryFactory { - var makeRepository: ((Request) -> BlogTagRepository)? - mutating func use(_ makeRepository: @escaping (Request) -> BlogTagRepository) { - self.makeRepository = makeRepository + + var blogRepositories: BlogRepositories { + .init(application: self) } } diff --git a/Tests/SteamPressTests/Helpers/InMemoryRepository.swift b/Tests/SteamPressTests/Helpers/InMemoryRepository.swift index b7b2eeab..7382f463 100644 --- a/Tests/SteamPressTests/Helpers/InMemoryRepository.swift +++ b/Tests/SteamPressTests/Helpers/InMemoryRepository.swift @@ -18,6 +18,10 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit } // MARK: - BlogTagRepository + + func `for`(_ request: Request) -> BlogTagRepository { + return self + } func getAllTags() -> EventLoopFuture<[BlogTag]> { return eventLoop.future(tags) @@ -130,6 +134,10 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit } // MARK: - BlogPostRepository + + func `for`(_ request: Request) -> BlogPostRepository { + return self + } func getAllPostsSortedByPublishDate(includeDrafts: Bool) -> EventLoopFuture<[BlogPost]> { var sortedPosts = posts.sorted { $0.created > $1.created } @@ -246,6 +254,10 @@ class InMemoryRepository: BlogTagRepository, BlogPostRepository, BlogUserReposit } // MARK: - BlogUserRepository + + func `for`(_ request: Request) -> BlogUserRepository { + return self + } func add(_ user: BlogUser) { if (users.first { $0.userID == user.userID } == nil) { diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift index e23e229c..ba4ca1f9 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift @@ -23,9 +23,8 @@ extension TestWorld { enableAuthorPages: enableAuthorPages, enableTagPages: enableTagPages) application.lifecycle.use(steampress) - - services.register([BlogTagRepository.self, BlogPostRepository.self, BlogUserRepository.self]) { _ in + application.blogRepositories.use { _ in return repository } diff --git a/Tests/SteamPressTests/ProviderTests.swift b/Tests/SteamPressTests/ProviderTests.swift index 4bb84941..8273b781 100644 --- a/Tests/SteamPressTests/ProviderTests.swift +++ b/Tests/SteamPressTests/ProviderTests.swift @@ -9,10 +9,9 @@ class ProviderTests: XCTestCase { app.middleware.use(BlogRememberMeMiddleware()) app.middleware.use(SessionsMiddleware(session: app.sessions.driver)) - #warning("TODO") -// services.register([BlogTagRepository.self, BlogPostRepository.self, BlogUserRepository.self]) { _ in -// return InMemoryRepository() -// } + app.blogRepositories.use { application in + return InMemoryRepository(eventLoop: application.eventLoopGroup.next()) + } let numberGenerator = app.randomNumberGenerators.generator XCTAssertTrue(type(of: numberGenerator) == RealRandomNumberGenerator.self) From ba9537c837847ac20e81e1cb18d4cd0a4a8ef217 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Sun, 5 Apr 2020 07:46:24 +0100 Subject: [PATCH 43/70] Remove some warnings --- .../APITests/APITagControllerTests.swift | 2 +- .../AdminTests/AccessControlTests.swift | 2 +- .../AdminTests/AdminPageTests.swift | 2 +- .../AdminTests/AdminPostTests.swift | 2 +- .../AdminTests/AdminUserTests.swift | 2 +- .../SteamPressTests/AdminTests/LoginTests.swift | 2 +- .../SteamPressTests/BlogTests/AuthorTests.swift | 4 ++-- .../BlogTests/DisabledBlogTagTests.swift | 2 +- .../SteamPressTests/BlogTests/IndexTests.swift | 2 +- Tests/SteamPressTests/BlogTests/PostTests.swift | 2 +- .../SteamPressTests/BlogTests/SearchTests.swift | 4 ++-- Tests/SteamPressTests/BlogTests/TagTests.swift | 4 ++-- .../Feed Tests/AtomFeedTests.swift | 4 ++-- .../Feed Tests/RSSFeedTests.swift | 4 ++-- .../Helpers/TestWorld+TestDataBuilder.swift | 2 +- Tests/SteamPressTests/Helpers/TestWorld.swift | 17 +---------------- 16 files changed, 21 insertions(+), 36 deletions(-) diff --git a/Tests/SteamPressTests/APITests/APITagControllerTests.swift b/Tests/SteamPressTests/APITests/APITagControllerTests.swift index c0376136..6d17cc03 100644 --- a/Tests/SteamPressTests/APITests/APITagControllerTests.swift +++ b/Tests/SteamPressTests/APITests/APITagControllerTests.swift @@ -17,7 +17,7 @@ class APITagControllerTests: XCTestCase { XCTAssertEqual(tags[0].name, tag1.name) XCTAssertEqual(tags[1].name, tag2.name) - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } } diff --git a/Tests/SteamPressTests/AdminTests/AccessControlTests.swift b/Tests/SteamPressTests/AdminTests/AccessControlTests.swift index 96a787d2..461be22c 100644 --- a/Tests/SteamPressTests/AdminTests/AccessControlTests.swift +++ b/Tests/SteamPressTests/AdminTests/AccessControlTests.swift @@ -17,7 +17,7 @@ class AccessControlTests: XCTestCase { } override func tearDown() { - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } // MARK: - Tests diff --git a/Tests/SteamPressTests/AdminTests/AdminPageTests.swift b/Tests/SteamPressTests/AdminTests/AdminPageTests.swift index 29d069ed..aa35d056 100644 --- a/Tests/SteamPressTests/AdminTests/AdminPageTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminPageTests.swift @@ -24,6 +24,6 @@ class AdminPageTests: XCTestCase { XCTAssertEqual(presenter.adminViewPageInformation?.websiteURL.absoluteString, "/") XCTAssertEqual(presenter.adminViewPageInformation?.currentPageURL.absoluteString, "/admin") - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } } diff --git a/Tests/SteamPressTests/AdminTests/AdminPostTests.swift b/Tests/SteamPressTests/AdminTests/AdminPostTests.swift index 9f99fd79..8ef1d48b 100644 --- a/Tests/SteamPressTests/AdminTests/AdminPostTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminPostTests.swift @@ -21,7 +21,7 @@ class AdminPostTests: XCTestCase { } override func tearDown() { - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } // MARK: - Post Creation diff --git a/Tests/SteamPressTests/AdminTests/AdminUserTests.swift b/Tests/SteamPressTests/AdminTests/AdminUserTests.swift index 37043cbd..dc67a0c2 100644 --- a/Tests/SteamPressTests/AdminTests/AdminUserTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminUserTests.swift @@ -21,7 +21,7 @@ class AdminUserTests: XCTestCase { } override func tearDown() { - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } // MARK: - User Creation diff --git a/Tests/SteamPressTests/AdminTests/LoginTests.swift b/Tests/SteamPressTests/AdminTests/LoginTests.swift index fb50ada4..c19841e7 100644 --- a/Tests/SteamPressTests/AdminTests/LoginTests.swift +++ b/Tests/SteamPressTests/AdminTests/LoginTests.swift @@ -26,7 +26,7 @@ class LoginTests: XCTestCase { } override func tearDown() { - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } // MARK: - Tests diff --git a/Tests/SteamPressTests/BlogTests/AuthorTests.swift b/Tests/SteamPressTests/BlogTests/AuthorTests.swift index 6ffb7093..f73fe4ae 100644 --- a/Tests/SteamPressTests/BlogTests/AuthorTests.swift +++ b/Tests/SteamPressTests/BlogTests/AuthorTests.swift @@ -25,7 +25,7 @@ class AuthorTests: XCTestCase { } override func tearDown() { - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } // MARK: - Tests @@ -163,7 +163,7 @@ class AuthorTests: XCTestCase { let tag2Name = "Search" let tag1 = try testWorld.createTag(tag1Name, on: post2.post) _ = try testWorld.createTag(tag2Name, on: postData.post) - try testWorld.context.repository.add(tag1, to: postData.post) + try testWorld.context.repository.internalAdd(tag1, to: postData.post) _ = try testWorld.getResponse(to: "/authors/leia") let tagsForPosts = try XCTUnwrap(presenter.authorPageTagsForPost) diff --git a/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift b/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift index 09e2b72c..e9b3c5c0 100644 --- a/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift +++ b/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift @@ -14,6 +14,6 @@ class DisabledBlogTagTests: XCTestCase { tagResponse = nil allTagsResponse = nil - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } } diff --git a/Tests/SteamPressTests/BlogTests/IndexTests.swift b/Tests/SteamPressTests/BlogTests/IndexTests.swift index 715fdc43..7b0ab50e 100644 --- a/Tests/SteamPressTests/BlogTests/IndexTests.swift +++ b/Tests/SteamPressTests/BlogTests/IndexTests.swift @@ -22,7 +22,7 @@ class IndexTests: XCTestCase { } override func tearDown() { - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } // MARK: - Tests diff --git a/Tests/SteamPressTests/BlogTests/PostTests.swift b/Tests/SteamPressTests/BlogTests/PostTests.swift index 53a9eca2..f0eef443 100644 --- a/Tests/SteamPressTests/BlogTests/PostTests.swift +++ b/Tests/SteamPressTests/BlogTests/PostTests.swift @@ -22,7 +22,7 @@ class PostTests: XCTestCase { } override func tearDown() { - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } // MARK: - Tests diff --git a/Tests/SteamPressTests/BlogTests/SearchTests.swift b/Tests/SteamPressTests/BlogTests/SearchTests.swift index 13b09ddc..92c8c88c 100644 --- a/Tests/SteamPressTests/BlogTests/SearchTests.swift +++ b/Tests/SteamPressTests/BlogTests/SearchTests.swift @@ -21,7 +21,7 @@ class SearchTests: XCTestCase { } override func tearDown() { - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } // MARK: - Tests @@ -94,7 +94,7 @@ class SearchTests: XCTestCase { let tag2Name = "Search" let tag1 = try testWorld.createTag(tag1Name, on: post2.post) _ = try testWorld.createTag(tag2Name, on: firstData.post) - try testWorld.context.repository.add(tag1, to: firstData.post) + try testWorld.context.repository.internalAll(tag1, to: firstData.post) _ = try testWorld.getResponse(to: "/search?term=Test") let tagsForPosts = try XCTUnwrap(presenter.searchPageTagsForPost) diff --git a/Tests/SteamPressTests/BlogTests/TagTests.swift b/Tests/SteamPressTests/BlogTests/TagTests.swift index d1867488..e2be0595 100644 --- a/Tests/SteamPressTests/BlogTests/TagTests.swift +++ b/Tests/SteamPressTests/BlogTests/TagTests.swift @@ -26,7 +26,7 @@ class TagTests: XCTestCase { } override func tearDown() { - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } // MARK: - Tests @@ -35,7 +35,7 @@ class TagTests: XCTestCase { let secondPost = try! testWorld.createPost() let thirdPost = try! testWorld.createPost() let secondTag = try testWorld.createTag("AnotherTag", on: secondPost.post) - try testWorld.context.repository.add(secondTag, to: thirdPost.post) + try testWorld.context.repository.internalAll(secondTag, to: thirdPost.post) _ = try testWorld.getResponse(to: allTagsRequestPath) XCTAssertEqual(presenter.allTagsPageTags?.count, 2) diff --git a/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift b/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift index 6bdcdd7b..f27d94ba 100644 --- a/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift +++ b/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift @@ -19,7 +19,7 @@ class AtomFeedTests: XCTestCase { } override func tearDown() { - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } // MARK: - Tests @@ -216,7 +216,7 @@ class AtomFeedTests: XCTestCase { func testCorrectHeaderSetForAtomFeed() throws { testWorld = try TestWorld.create() let actualXmlResponse = try testWorld.getResponse(to: atomPath) - XCTAssertEqual(actualXmlResponse.headers.firstValue(name: .contentType), "application/atom+xml") + XCTAssertEqual(actualXmlResponse.headers.first(name: .contentType), "application/atom+xml") } func testThatDateFormatterIsCorrect() throws { diff --git a/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift b/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift index aeafacb7..168c1f4f 100644 --- a/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift +++ b/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift @@ -19,7 +19,7 @@ class RSSFeedTests: XCTestCase { } override func tearDown() { - XCTAssertNoThrow(try testWorld.tryAsHardAsWeCanToShutdownApplication()) + XCTAssertNoThrow(try testWorld.shutdown()) } // MARK: - Tests @@ -187,7 +187,7 @@ class RSSFeedTests: XCTestCase { testWorld = try TestWorld.create() let actualXmlResponse = try testWorld.getResponse(to: rssPath) - XCTAssertEqual(actualXmlResponse.headers.firstValue(name: .contentType), "application/rss+xml") + XCTAssertEqual(actualXmlResponse.headers.first(name: .contentType), "application/rss+xml") } func testThatDateFormatterIsCorrect() throws { diff --git a/Tests/SteamPressTests/Helpers/TestWorld+TestDataBuilder.swift b/Tests/SteamPressTests/Helpers/TestWorld+TestDataBuilder.swift index e435f528..45a44228 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+TestDataBuilder.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+TestDataBuilder.swift @@ -10,7 +10,7 @@ extension TestWorld { for index in 1...count { let data = try createPost(title: "Post \(index)", slugUrl: "post-\(index)", author: author) if let tag = tag { - try context.repository.add(tag, to: data.post) + try context.repository.internalAdd(tag, to: data.post) } } } diff --git a/Tests/SteamPressTests/Helpers/TestWorld.swift b/Tests/SteamPressTests/Helpers/TestWorld.swift index f0d5346e..1f07a0fb 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld.swift @@ -32,23 +32,8 @@ struct TestWorld { let eventLoopGroup: EventLoopGroup } - #warning("Remove or rename") - // To work around Vapor 3 dodgy lifecycle mess - mutating func tryAsHardAsWeCanToShutdownApplication() throws { + mutating func shutdown() throws { context.app.shutdown() try context.eventLoopGroup.syncShutdownGracefully() -// struct ApplicationDidNotGoAway: Error { -// var description: String -// } -// weak var weakApp: Application? = context.app -// context.app = nil -// var tries = 0 -// while weakApp != nil && tries < 10 { -// Thread.sleep(forTimeInterval: 0.1) -// tries += 1 -// } -// if weakApp != nil { -// throw ApplicationDidNotGoAway(description: "application leak: \(weakApp.debugDescription)") -// } } } From 6c3a4b50e68efa551125f7727dab5920a3837fa7 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Sun, 5 Apr 2020 08:08:28 +0100 Subject: [PATCH 44/70] It compiles! --- .../SteamPressTests/BlogTests/SearchTests.swift | 2 +- Tests/SteamPressTests/BlogTests/TagTests.swift | 2 +- .../Helpers/TestWorld+Responses.swift | 16 +++++----------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/Tests/SteamPressTests/BlogTests/SearchTests.swift b/Tests/SteamPressTests/BlogTests/SearchTests.swift index 92c8c88c..8f3329cc 100644 --- a/Tests/SteamPressTests/BlogTests/SearchTests.swift +++ b/Tests/SteamPressTests/BlogTests/SearchTests.swift @@ -94,7 +94,7 @@ class SearchTests: XCTestCase { let tag2Name = "Search" let tag1 = try testWorld.createTag(tag1Name, on: post2.post) _ = try testWorld.createTag(tag2Name, on: firstData.post) - try testWorld.context.repository.internalAll(tag1, to: firstData.post) + try testWorld.context.repository.internalAdd(tag1, to: firstData.post) _ = try testWorld.getResponse(to: "/search?term=Test") let tagsForPosts = try XCTUnwrap(presenter.searchPageTagsForPost) diff --git a/Tests/SteamPressTests/BlogTests/TagTests.swift b/Tests/SteamPressTests/BlogTests/TagTests.swift index e2be0595..20c2737e 100644 --- a/Tests/SteamPressTests/BlogTests/TagTests.swift +++ b/Tests/SteamPressTests/BlogTests/TagTests.swift @@ -35,7 +35,7 @@ class TagTests: XCTestCase { let secondPost = try! testWorld.createPost() let thirdPost = try! testWorld.createPost() let secondTag = try testWorld.createTag("AnotherTag", on: secondPost.post) - try testWorld.context.repository.internalAll(secondTag, to: thirdPost.post) + try testWorld.context.repository.internalAdd(secondTag, to: thirdPost.post) _ = try testWorld.getResponse(to: allTagsRequestPath) XCTAssertEqual(presenter.allTagsPageTags?.count, 2) diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Responses.swift b/Tests/SteamPressTests/Helpers/TestWorld+Responses.swift index 16aeb4ec..2c2de8db 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Responses.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Responses.swift @@ -8,8 +8,7 @@ extension TestWorld { } func getResponseString(to path: String, headers: HTTPHeaders = .init()) throws -> String { - let data = try getResponse(to: path, headers: headers).body.convertToHTTPBody().data - return String(data: data!, encoding: .utf8)! + return try getResponse(to: path, headers: headers).body.string! } func getResponse(to path: String, method: HTTPMethod = .POST, body: T, loggedInUser: BlogUser? = nil, passwordToLoginWith: String? = nil, headers: HTTPHeaders = .init()) throws -> Response { @@ -24,16 +23,12 @@ extension TestWorld { } func setupRequest(to path: String, method: HTTPMethod = .POST, loggedInUser: BlogUser? = nil, passwordToLoginWith: String? = nil, headers: HTTPHeaders = .init()) throws -> Request { - var request = HTTPRequest(method: method, url: URL(string: path)!, headers: headers) + let request = Request(application: context.app, method: method, url: URI(path: path), headers: headers, on: context.eventLoopGroup.next()) request.cookies["steampress-session"] = try setLoginCookie(for: loggedInUser, password: passwordToLoginWith) - - guard let app = context.app else { - fatalError("App has already been deinitiliased") - } - return Request(http: request, using: app) + return request } - func setLoginCookie(for user: BlogUser?, password: String? = nil) throws -> HTTPSetCookie? { + func setLoginCookie(for user: BlogUser?, password: String? = nil) throws -> HTTPCookies.Value? { if let user = user { let loginData = LoginData(username: user.username, password: password ?? user.password) var loginPath = "/admin/login" @@ -49,7 +44,6 @@ extension TestWorld { } func getResponse(to request: Request) throws -> Response { - let responder = try context.app.make(Responder.self) - return try responder.respond(to: request).wait() + return try context.app.responder.respond(to: request).wait() } } From c65ec9396bb00e3028588867c2691fefc528909a Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Sun, 5 Apr 2020 08:34:47 +0100 Subject: [PATCH 45/70] Stop some tests crashing --- .../Repositories/SteamPressRepository.swift | 2 +- Sources/SteamPress/SteampressLifecyle.swift | 18 ++++++++++++------ .../Helpers/TestWorld+Application.swift | 4 +++- Tests/SteamPressTests/Helpers/TestWorld.swift | 8 +++++--- Tests/SteamPressTests/ProviderTests.swift | 4 +++- .../ViewTests/BlogViewTests.swift | 2 +- 6 files changed, 25 insertions(+), 13 deletions(-) diff --git a/Sources/SteamPress/Repositories/SteamPressRepository.swift b/Sources/SteamPress/Repositories/SteamPressRepository.swift index 28be8905..737ce667 100644 --- a/Sources/SteamPress/Repositories/SteamPressRepository.swift +++ b/Sources/SteamPress/Repositories/SteamPressRepository.swift @@ -116,7 +116,7 @@ public extension Application { self.storage.makePostRepository = makeRespository } - func initialize() { + public func initialize() { self.application.storage[Key.self] = .init() } diff --git a/Sources/SteamPress/SteampressLifecyle.swift b/Sources/SteamPress/SteampressLifecyle.swift index 04c79485..97e0286d 100644 --- a/Sources/SteamPress/SteampressLifecyle.swift +++ b/Sources/SteamPress/SteampressLifecyle.swift @@ -35,17 +35,23 @@ public struct SteampressLifecyle: LifecycleHandler { self.enableTagPages = enableTagPages self.pathCreator = BlogPathCreator(blogPath: self.blogPath) } - -// public func register(_ services: inout Services) throws { -// services.register(BlogRememberMeMiddleware.self) -// } - - public func willBoot(_ application: Application) throws { + + public func tmpSetup(_ application: Application) { application.randomNumberGenerators.initialize() application.blogPresenters.initialize() application.adminPresenters.initialize(pathCreator: pathCreator) application.passwordHashers.initialize() application.passwordVerifiers.initialize() + application.blogRepositories.initialize() + } + + public func willBoot(_ application: Application) throws { +// application.randomNumberGenerators.initialize() +// application.blogPresenters.initialize() +// application.adminPresenters.initialize(pathCreator: pathCreator) +// application.passwordHashers.initialize() +// application.passwordVerifiers.initialize() +// application.blogRepositories.initialize() let router = application.routes diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift index ba4ca1f9..6c9f36db 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift @@ -24,6 +24,8 @@ extension TestWorld { enableTagPages: enableTagPages) application.lifecycle.use(steampress) + #warning("This should be removed") + steampress.tmpSetup(application) application.blogRepositories.use { _ in return repository } @@ -48,6 +50,6 @@ extension TestWorld { application.passwordHashers.use(.reversed) } - return Application(.testing, .shared(eventLoopGroup)) + return application } } diff --git a/Tests/SteamPressTests/Helpers/TestWorld.swift b/Tests/SteamPressTests/Helpers/TestWorld.swift index 1f07a0fb..3c4a17e1 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld.swift @@ -14,17 +14,19 @@ struct TestWorld { unsetenv("BLOG_SITE_TWITTER_HANDLE") unsetenv("BLOG_DISQUS_NAME") unsetenv("WEBSITE_URL") + #warning("When do we do this?") + //try application.boot() return TestWorld(context: context) } - var context: Context + let context: Context init(context: Context) { self.context = context } struct Context { - var app: Application + let app: Application let repository: InMemoryRepository let blogPresenter: CapturingBlogPresenter let blogAdminPresenter: CapturingAdminPresenter @@ -32,7 +34,7 @@ struct TestWorld { let eventLoopGroup: EventLoopGroup } - mutating func shutdown() throws { + func shutdown() throws { context.app.shutdown() try context.eventLoopGroup.syncShutdownGracefully() } diff --git a/Tests/SteamPressTests/ProviderTests.swift b/Tests/SteamPressTests/ProviderTests.swift index 8273b781..33933c4c 100644 --- a/Tests/SteamPressTests/ProviderTests.swift +++ b/Tests/SteamPressTests/ProviderTests.swift @@ -5,7 +5,9 @@ import Vapor class ProviderTests: XCTestCase { func testUsingProviderSetsCorrectServices() throws { let app = Application() - app.lifecycle.use(SteamPress.SteampressLifecyle()) + let lifecycle = SteamPress.SteampressLifecyle() + lifecycle.tmpSetup(app) + app.lifecycle.use(lifecycle) app.middleware.use(BlogRememberMeMiddleware()) app.middleware.use(SessionsMiddleware(session: app.sessions.driver)) diff --git a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift index ec3477fc..8c7b9c96 100644 --- a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift @@ -20,7 +20,7 @@ class BlogViewTests: XCTestCase { eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) presenter = ViewBlogPresenter(viewRenderer: viewRenderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: eventLoopGroup) - author.userID = 1 + author = TestDataBuilder.anyUser(id: 1) let createdDate = Date(timeIntervalSince1970: 1584714638) let lastEditedDate = Date(timeIntervalSince1970: 1584981458) post = try! TestDataBuilder.anyPost(author: author, contents: TestDataBuilder.longContents, creationDate: createdDate, lastEditedDate: lastEditedDate) From c3abc6aec794e047204b3ad8323e06885be84ace Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Sun, 5 Apr 2020 08:39:57 +0100 Subject: [PATCH 46/70] Actually register routes --- Tests/SteamPressTests/APITests/APITagControllerTests.swift | 2 +- Tests/SteamPressTests/Helpers/TestWorld.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SteamPressTests/APITests/APITagControllerTests.swift b/Tests/SteamPressTests/APITests/APITagControllerTests.swift index 6d17cc03..89816467 100644 --- a/Tests/SteamPressTests/APITests/APITagControllerTests.swift +++ b/Tests/SteamPressTests/APITests/APITagControllerTests.swift @@ -7,7 +7,7 @@ class APITagControllerTests: XCTestCase { // MARK: - Tests func testThatAllTagsAreReturnedFromAPI() throws { - var testWorld = try TestWorld.create() + let testWorld = try TestWorld.create() let tag1 = try testWorld.context.repository.addTag(name: "Vapor3") let tag2 = try testWorld.context.repository.addTag(name: "Engineering") diff --git a/Tests/SteamPressTests/Helpers/TestWorld.swift b/Tests/SteamPressTests/Helpers/TestWorld.swift index 3c4a17e1..1c4525d1 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld.swift @@ -15,7 +15,7 @@ struct TestWorld { unsetenv("BLOG_DISQUS_NAME") unsetenv("WEBSITE_URL") #warning("When do we do this?") - //try application.boot() + try application.boot() return TestWorld(context: context) } From 7432e274bbc1399c9c593f55b2301544330b294e Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Mon, 6 Apr 2020 13:11:27 +0100 Subject: [PATCH 47/70] The setup methods don't need to throw anymore --- .../APITests/APITagControllerTests.swift | 2 +- .../AdminTests/AccessControlTests.swift | 2 +- .../AdminTests/AdminPageTests.swift | 2 +- .../AdminTests/AdminPostTests.swift | 4 +-- .../AdminTests/AdminUserTests.swift | 8 ++--- .../AdminTests/LoginTests.swift | 4 +-- .../BlogTests/AuthorTests.swift | 4 +-- .../BlogTests/DisabledBlogTagTests.swift | 2 +- .../BlogTests/IndexTests.swift | 6 ++-- .../SteamPressTests/BlogTests/PostTests.swift | 2 +- .../BlogTests/SearchTests.swift | 2 +- .../SteamPressTests/BlogTests/TagTests.swift | 2 +- .../Feed Tests/AtomFeedTests.swift | 32 +++++++++---------- .../Feed Tests/RSSFeedTests.swift | 32 +++++++++---------- .../Helpers/TestWorld+Application.swift | 2 +- Tests/SteamPressTests/Helpers/TestWorld.swift | 8 ++--- 16 files changed, 57 insertions(+), 57 deletions(-) diff --git a/Tests/SteamPressTests/APITests/APITagControllerTests.swift b/Tests/SteamPressTests/APITests/APITagControllerTests.swift index 89816467..0e0ee499 100644 --- a/Tests/SteamPressTests/APITests/APITagControllerTests.swift +++ b/Tests/SteamPressTests/APITests/APITagControllerTests.swift @@ -7,7 +7,7 @@ class APITagControllerTests: XCTestCase { // MARK: - Tests func testThatAllTagsAreReturnedFromAPI() throws { - let testWorld = try TestWorld.create() + let testWorld = TestWorld.create() let tag1 = try testWorld.context.repository.addTag(name: "Vapor3") let tag2 = try testWorld.context.repository.addTag(name: "Engineering") diff --git a/Tests/SteamPressTests/AdminTests/AccessControlTests.swift b/Tests/SteamPressTests/AdminTests/AccessControlTests.swift index 461be22c..3ff12269 100644 --- a/Tests/SteamPressTests/AdminTests/AccessControlTests.swift +++ b/Tests/SteamPressTests/AdminTests/AccessControlTests.swift @@ -12,7 +12,7 @@ class AccessControlTests: XCTestCase { // MARK: - Overrides override func setUp() { - testWorld = try! TestWorld.create(path: "blog") + testWorld = TestWorld.create(path: "blog") user = testWorld.createUser() } diff --git a/Tests/SteamPressTests/AdminTests/AdminPageTests.swift b/Tests/SteamPressTests/AdminTests/AdminPageTests.swift index aa35d056..d41f2024 100644 --- a/Tests/SteamPressTests/AdminTests/AdminPageTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminPageTests.swift @@ -5,7 +5,7 @@ import SteamPress class AdminPageTests: XCTestCase { func testAdminPagePassesCorrectInformationToPresenter() throws { - var testWorld = try TestWorld.create() + let testWorld = TestWorld.create() let user = testWorld.createUser(username: "leia") let testData1 = try testWorld.createPost(author: user) let testData2 = try testWorld.createPost(title: "A second post", author: user) diff --git a/Tests/SteamPressTests/AdminTests/AdminPostTests.swift b/Tests/SteamPressTests/AdminTests/AdminPostTests.swift index 8ef1d48b..e298f4bd 100644 --- a/Tests/SteamPressTests/AdminTests/AdminPostTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminPostTests.swift @@ -16,7 +16,7 @@ class AdminPostTests: XCTestCase { // MARK: - Overrides override func setUp() { - testWorld = try! TestWorld.create() + testWorld = TestWorld.create() user = testWorld.createUser(username: "leia") } @@ -59,7 +59,7 @@ class AdminPostTests: XCTestCase { func testCreatingPostWithNonUniqueSlugFromSameTitle() throws { let randomNumber = 345 - testWorld = try TestWorld.create(randomNumberGenerator: StubbedRandomNumberGenerator(numberToReturn: randomNumber)) + testWorld = TestWorld.create(randomNumberGenerator: StubbedRandomNumberGenerator(numberToReturn: randomNumber)) let initialPostData = try testWorld.createPost(title: "Post Title", slugUrl: "post-title") struct CreatePostData: Content { diff --git a/Tests/SteamPressTests/AdminTests/AdminUserTests.swift b/Tests/SteamPressTests/AdminTests/AdminUserTests.swift index dc67a0c2..fe22289c 100644 --- a/Tests/SteamPressTests/AdminTests/AdminUserTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminUserTests.swift @@ -16,7 +16,7 @@ class AdminUserTests: XCTestCase { // MARK: - Overrides override func setUp() { - testWorld = try! TestWorld.create() + testWorld = TestWorld.create() user = testWorld.createUser(name: "Leia", username: "leia") } @@ -348,7 +348,7 @@ class AdminUserTests: XCTestCase { } func testPasswordIsActuallyHashedWhenCreatingAUser() throws { - testWorld = try! TestWorld.create(passwordHasherToUse: .reversed) + testWorld = TestWorld.create(passwordHasherToUse: .reversed) let usersPassword = "password" let hashedPassword = String(usersPassword.reversed()) user = testWorld.createUser(name: "Leia", username: "leia", password: hashedPassword) @@ -637,7 +637,7 @@ class AdminUserTests: XCTestCase { } func testPasswordIsActuallyHashedWhenEditingAUser() throws { - testWorld = try! TestWorld.create(passwordHasherToUse: .reversed) + testWorld = TestWorld.create(passwordHasherToUse: .reversed) let usersPassword = "password" let hashedPassword = String(usersPassword.reversed()) user = testWorld.createUser(name: "Leia", username: "leia", password: hashedPassword) @@ -723,7 +723,7 @@ class AdminUserTests: XCTestCase { } func testCannotDeleteLastUser() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let adminUser = testWorld.createUser(name: "Admin", username: "admin") let testData = try testWorld.createPost(author: adminUser) _ = try testWorld.getResponse(to: "/admin/users/\(adminUser.userID!)/delete", body: EmptyContent(), loggedInUser: adminUser) diff --git a/Tests/SteamPressTests/AdminTests/LoginTests.swift b/Tests/SteamPressTests/AdminTests/LoginTests.swift index c19841e7..b03168f7 100644 --- a/Tests/SteamPressTests/AdminTests/LoginTests.swift +++ b/Tests/SteamPressTests/AdminTests/LoginTests.swift @@ -21,7 +21,7 @@ class LoginTests: XCTestCase { // MARK: - Overrides override func setUp() { - testWorld = try! TestWorld.create(path: "blog") + testWorld = TestWorld.create(path: "blog") user = testWorld.createUser() } @@ -32,7 +32,7 @@ class LoginTests: XCTestCase { // MARK: - Tests func testLogin() throws { - testWorld = try TestWorld.create(path: "blog", passwordHasherToUse: .real) + testWorld = TestWorld.create(path: "blog", passwordHasherToUse: .real) let hashedPassword = try BCryptDigest().hash("password") user = testWorld.createUser(password: hashedPassword) let loginData = LoginData(username: user.username, password: "password") diff --git a/Tests/SteamPressTests/BlogTests/AuthorTests.swift b/Tests/SteamPressTests/BlogTests/AuthorTests.swift index f73fe4ae..00b314b9 100644 --- a/Tests/SteamPressTests/BlogTests/AuthorTests.swift +++ b/Tests/SteamPressTests/BlogTests/AuthorTests.swift @@ -19,7 +19,7 @@ class AuthorTests: XCTestCase { // MARK: - Overrides override func setUp() { - testWorld = try! TestWorld.create(postsPerPage: postsPerPage) + testWorld = TestWorld.create(postsPerPage: postsPerPage) user = testWorld.createUser(username: "leia") postData = try! testWorld.createPost(author: user) } @@ -53,7 +53,7 @@ class AuthorTests: XCTestCase { } func testDisabledBlogAuthorsPath() throws { - testWorld = try TestWorld.create(enableAuthorPages: false) + testWorld = TestWorld.create(enableAuthorPages: false) _ = testWorld.createUser(username: "leia") let authorResponse = try testWorld.getResponse(to: authorsRequestPath) diff --git a/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift b/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift index e9b3c5c0..1f863979 100644 --- a/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift +++ b/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift @@ -3,7 +3,7 @@ import Vapor class DisabledBlogTagTests: XCTestCase { func testDisabledBlogTagsPath() throws { - var testWorld = try TestWorld.create(enableTagPages: false) + let testWorld = TestWorld.create(enableTagPages: false) _ = try testWorld.createTag("Engineering") var tagResponse: Response? = try testWorld.getResponse(to: "/tags/Engineering") var allTagsResponse: Response? = try testWorld.getResponse(to: "/tags") diff --git a/Tests/SteamPressTests/BlogTests/IndexTests.swift b/Tests/SteamPressTests/BlogTests/IndexTests.swift index 7b0ab50e..b581c1c7 100644 --- a/Tests/SteamPressTests/BlogTests/IndexTests.swift +++ b/Tests/SteamPressTests/BlogTests/IndexTests.swift @@ -17,7 +17,7 @@ class IndexTests: XCTestCase { // MARK: - Overrides override func setUp() { - testWorld = try! TestWorld.create(postsPerPage: postsPerPage) + testWorld = TestWorld.create(postsPerPage: postsPerPage) firstData = try! testWorld.createPost(title: "Test Path", slugUrl: "test-path") } @@ -76,7 +76,7 @@ class IndexTests: XCTestCase { } func testThatAccessingPathsRouteRedirectsToBlogIndexWithCustomPath() throws { - testWorld = try! TestWorld.create(path: "blog") + testWorld = TestWorld.create(path: "blog") let response = try testWorld.getResponse(to: "/blog/posts/") XCTAssertEqual(response.status, .movedPermanently) XCTAssertEqual(response.headers[.location].first, "/blog/") @@ -135,7 +135,7 @@ class IndexTests: XCTestCase { } func testIndexPageCurrentPageWhenAtSubPath() throws { - testWorld = try TestWorld.create(path: "blog") + testWorld = TestWorld.create(path: "blog") _ = try testWorld.getResponse(to: "/blog") XCTAssertEqual(presenter.indexPageInformation?.currentPageURL.absoluteString, "/blog") XCTAssertEqual(presenter.indexPageInformation?.websiteURL.absoluteString, "/") diff --git a/Tests/SteamPressTests/BlogTests/PostTests.swift b/Tests/SteamPressTests/BlogTests/PostTests.swift index f0eef443..dd51c42f 100644 --- a/Tests/SteamPressTests/BlogTests/PostTests.swift +++ b/Tests/SteamPressTests/BlogTests/PostTests.swift @@ -17,7 +17,7 @@ class PostTests: XCTestCase { // MARK: - Overrides override func setUpWithError() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() firstData = try testWorld.createPost(title: "Test Path", slugUrl: "test-path") } diff --git a/Tests/SteamPressTests/BlogTests/SearchTests.swift b/Tests/SteamPressTests/BlogTests/SearchTests.swift index 8f3329cc..c431a91b 100644 --- a/Tests/SteamPressTests/BlogTests/SearchTests.swift +++ b/Tests/SteamPressTests/BlogTests/SearchTests.swift @@ -16,7 +16,7 @@ class SearchTests: XCTestCase { // MARK: - Overrides override func setUpWithError() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() firstData = try testWorld.createPost(title: "Test Path", slugUrl: "test-path") } diff --git a/Tests/SteamPressTests/BlogTests/TagTests.swift b/Tests/SteamPressTests/BlogTests/TagTests.swift index 20c2737e..6c8cf4c8 100644 --- a/Tests/SteamPressTests/BlogTests/TagTests.swift +++ b/Tests/SteamPressTests/BlogTests/TagTests.swift @@ -20,7 +20,7 @@ class TagTests: XCTestCase { // MARK: - Overrides override func setUp() { - testWorld = try! TestWorld.create(postsPerPage: postsPerPage) + testWorld = TestWorld.create(postsPerPage: postsPerPage) postData = try! testWorld.createPost() tag = try! testWorld.createTag(tagName, on: postData.post) } diff --git a/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift b/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift index f27d94ba..d5aec9e2 100644 --- a/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift +++ b/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift @@ -25,7 +25,7 @@ class AtomFeedTests: XCTestCase { // MARK: - Tests func testNoPostsReturnsCorrectAtomFeed() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" @@ -37,7 +37,7 @@ class AtomFeedTests: XCTestCase { func testThatFeedTitleCanBeConfigured() throws { let title = "My Awesome Blog" let feedInformation = FeedInformation(title: title) - testWorld = try TestWorld.create(feedInformation: feedInformation) + testWorld = TestWorld.create(feedInformation: feedInformation) let expectedXML = "\n\n\n\(title)\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" @@ -48,7 +48,7 @@ class AtomFeedTests: XCTestCase { func testThatFeedSubtitleCanBeConfigured() throws { let description = "This is a test for my blog" let feedInformation = FeedInformation(description: description) - testWorld = try TestWorld.create(feedInformation: feedInformation) + testWorld = TestWorld.create(feedInformation: feedInformation) let expectedXML = "\n\n\nSteamPress Blog\n\(description)\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" @@ -59,7 +59,7 @@ class AtomFeedTests: XCTestCase { func testThatRightsCanBeConifgured() throws { let copyright = "Copyright ©️ 2019 SteamPress" let feedInformation = FeedInformation(copyright: copyright) - testWorld = try TestWorld.create(feedInformation: feedInformation) + testWorld = TestWorld.create(feedInformation: feedInformation) let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\(copyright)\n" @@ -68,7 +68,7 @@ class AtomFeedTests: XCTestCase { } func testThatLinksAreCorrectForFullURI() throws { - testWorld = try TestWorld.create(path: "blog") + testWorld = TestWorld.create(path: "blog") let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://geeks.brokenhands.io/blog/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" let fullPath = "/blog/atom.xml" @@ -80,7 +80,7 @@ class AtomFeedTests: XCTestCase { } func testThatHTTPSLinksWorkWhenBehindReverseProxy() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://geeks.brokenhands.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" let fullPath = "/atom.xml" @@ -94,7 +94,7 @@ class AtomFeedTests: XCTestCase { func testThatLogoCanBeConfigured() throws { let imageURL = "https://static.brokenhands.io/images/feeds/atom.png" let feedInformation = FeedInformation(imageURL: imageURL) - testWorld = try TestWorld.create(feedInformation: feedInformation) + testWorld = TestWorld.create(feedInformation: feedInformation) let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\(imageURL)\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) @@ -102,7 +102,7 @@ class AtomFeedTests: XCTestCase { } func testThatFeedIsCorrectForOnePost() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let testData = try testWorld.createPost() let post = testData.post @@ -115,7 +115,7 @@ class AtomFeedTests: XCTestCase { } func testThatFeedIsCorrectForOnePostUnderPath() throws { - testWorld = try TestWorld.create(path: "blog") + testWorld = TestWorld.create(path: "blog") let testData = try testWorld.createPost() let post = testData.post @@ -128,7 +128,7 @@ class AtomFeedTests: XCTestCase { } func testThatFeedCorrectForTwoPosts() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let testData = try testWorld.createPost() let post = testData.post @@ -145,7 +145,7 @@ class AtomFeedTests: XCTestCase { } func testThatDraftsDontAppearInFeed() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let testData = try testWorld.createPost() let post = testData.post @@ -160,7 +160,7 @@ class AtomFeedTests: XCTestCase { } func testThatEditedPostsHaveUpdatedTimes() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let firstPostDate = Date().addingTimeInterval(-3600) let testData = try testWorld.createPost(createdDate: firstPostDate) @@ -180,7 +180,7 @@ class AtomFeedTests: XCTestCase { } func testThatTagsAppearWhenPostHasThem() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let tag1 = "Vapor 2" let tag2 = "Engineering" @@ -196,7 +196,7 @@ class AtomFeedTests: XCTestCase { } func testThatFullLinksWorksForPosts() throws { - testWorld = try TestWorld.create(path: "blog") + testWorld = TestWorld.create(path: "blog") let testData = try testWorld.createPost() @@ -214,13 +214,13 @@ class AtomFeedTests: XCTestCase { } func testCorrectHeaderSetForAtomFeed() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let actualXmlResponse = try testWorld.getResponse(to: atomPath) XCTAssertEqual(actualXmlResponse.headers.first(name: .contentType), "application/atom+xml") } func testThatDateFormatterIsCorrect() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let createDate = Date(timeIntervalSince1970: 1505867108) let testData = try testWorld.createPost(createdDate: createDate) diff --git a/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift b/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift index 168c1f4f..aeca4290 100644 --- a/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift +++ b/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift @@ -25,7 +25,7 @@ class RSSFeedTests: XCTestCase { // MARK: - Tests func testNoPostsReturnsCorrectRSSFeed() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n" @@ -34,7 +34,7 @@ class RSSFeedTests: XCTestCase { } func testOnePostReturnsCorrectRSSFeed() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let testData = try testWorld.createPost() let post = testData.post let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\n/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" @@ -44,7 +44,7 @@ class RSSFeedTests: XCTestCase { } func testMultiplePostsReturnsCorrectRSSFeed() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let testData = try testWorld.createPost() let post = testData.post let author = testData.author @@ -60,7 +60,7 @@ class RSSFeedTests: XCTestCase { } func testDraftsAreNotIncludedInFeed() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let testData = try testWorld.createPost() let post = testData.post @@ -75,7 +75,7 @@ class RSSFeedTests: XCTestCase { func testBlogTitleCanBeConfigured() throws { let title = "SteamPress - The Open Source Blog" let feedInformation = FeedInformation(title: title) - testWorld = try TestWorld.create(feedInformation: feedInformation) + testWorld = TestWorld.create(feedInformation: feedInformation) let testData = try testWorld.createPost() let post = testData.post @@ -89,7 +89,7 @@ class RSSFeedTests: XCTestCase { func testBlogDescriptionCanBeConfigured() throws { let description = "Our fancy new RSS-feed blog" let feedInformation = FeedInformation(description: description) - testWorld = try TestWorld.create(feedInformation: feedInformation) + testWorld = TestWorld.create(feedInformation: feedInformation) let testData = try testWorld.createPost() let post = testData.post @@ -101,7 +101,7 @@ class RSSFeedTests: XCTestCase { } func testRSSFeedEndpointAddedToCorrectEndpointWhenBlogInSubPath() throws { - testWorld = try TestWorld.create(path: "blog-path") + testWorld = TestWorld.create(path: "blog-path") let expectedXML = "\n\n\n\nSteamPress Blog\n/blog-path/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\nSearch SteamPress Blog\nSearch\n/blog-path/search?\nterm\n\n\n\n" @@ -110,7 +110,7 @@ class RSSFeedTests: XCTestCase { } func testPostLinkWhenBlogIsPlacedAtSubPath() throws { - testWorld = try TestWorld.create(path: "blog-path") + testWorld = TestWorld.create(path: "blog-path") let testData = try testWorld.createPost() let post = testData.post @@ -123,7 +123,7 @@ class RSSFeedTests: XCTestCase { func testCopyrightCanBeAddedToRSS() throws { let copyright = "Copyright ©️ 2017 SteamPress" let feedInformation = FeedInformation(copyright: copyright) - testWorld = try TestWorld.create(feedInformation: feedInformation) + testWorld = TestWorld.create(feedInformation: feedInformation) let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(copyright)\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n" @@ -132,7 +132,7 @@ class RSSFeedTests: XCTestCase { } func testThatTagsAreAddedToPostCorrectly() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let testData = try testWorld.createPost(tags: ["Vapor 2", "Engineering"]) let post = testData.post @@ -143,7 +143,7 @@ class RSSFeedTests: XCTestCase { } func testThatLinksComesFromRequestCorrectly() throws { - testWorld = try TestWorld.create(path: "blog-path") + testWorld = TestWorld.create(path: "blog-path") let testData = try testWorld.createPost() let post = testData.post @@ -158,7 +158,7 @@ class RSSFeedTests: XCTestCase { } func testThatLinksSpecifyHTTPSWhenComingFromReverseProxy() throws { - testWorld = try TestWorld.create(path: "blog-path") + testWorld = TestWorld.create(path: "blog-path") let testData = try testWorld.createPost() let post = testData.post @@ -175,7 +175,7 @@ class RSSFeedTests: XCTestCase { func testImageIsProvidedIfSupplied() throws { let image = "https://static.brokenhands.io/images/brokenhands.png" let feedInformation = FeedInformation(imageURL: image) - testWorld = try TestWorld.create(feedInformation: feedInformation) + testWorld = TestWorld.create(feedInformation: feedInformation) let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\n\(image)\nSteamPress Blog\n/\n\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n" @@ -184,7 +184,7 @@ class RSSFeedTests: XCTestCase { } func testCorrectHeaderSetForRSSFeed() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let actualXmlResponse = try testWorld.getResponse(to: rssPath) XCTAssertEqual(actualXmlResponse.headers.first(name: .contentType), "application/rss+xml") @@ -192,7 +192,7 @@ class RSSFeedTests: XCTestCase { func testThatDateFormatterIsCorrect() throws { let createDate = Date(timeIntervalSince1970: 1505867108) - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let testData = try testWorld.createPost(createdDate: createDate) let post = testData.post @@ -203,7 +203,7 @@ class RSSFeedTests: XCTestCase { } func testThatDescriptionContainsOnlyText() throws { - testWorld = try TestWorld.create() + testWorld = TestWorld.create() let contents = "[This is](https://www.google.com) a post that contains some **text**. \n# Formatting should be removed" let testData = try testWorld.createPost(contents: contents) let post = testData.post diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift index 6c9f36db..1c6087b2 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift @@ -12,7 +12,7 @@ extension TestWorld { enableAuthorPages: Bool, enableTagPages: Bool, passwordHasherToUse: PasswordHasherChoice, - randomNumberGenerator: StubbedRandomNumberGenerator) throws -> Application { + randomNumberGenerator: StubbedRandomNumberGenerator) -> Application { let application = Application(.testing, .shared(eventLoopGroup)) diff --git a/Tests/SteamPressTests/Helpers/TestWorld.swift b/Tests/SteamPressTests/Helpers/TestWorld.swift index 1c4525d1..ed1611a9 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld.swift @@ -3,19 +3,19 @@ import Vapor struct TestWorld { - static func create(path: String? = nil, postsPerPage: Int = 10, feedInformation: FeedInformation = FeedInformation(), enableAuthorPages: Bool = true, enableTagPages: Bool = true, passwordHasherToUse: PasswordHasherChoice = .plaintext, randomNumberGenerator: StubbedRandomNumberGenerator = StubbedRandomNumberGenerator(numberToReturn: 666)) throws -> TestWorld { + static func create(path: String? = nil, postsPerPage: Int = 10, feedInformation: FeedInformation = FeedInformation(), enableAuthorPages: Bool = true, enableTagPages: Bool = true, passwordHasherToUse: PasswordHasherChoice = .plaintext, randomNumberGenerator: StubbedRandomNumberGenerator = StubbedRandomNumberGenerator(numberToReturn: 666)) -> TestWorld { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) let repository = InMemoryRepository(eventLoop: eventLoopGroup.next()) let blogPresenter = CapturingBlogPresenter(eventLoop: eventLoopGroup.next()) let blogAdminPresenter = CapturingAdminPresenter(eventLoop: eventLoopGroup.next()) - let application = try TestWorld.getSteamPressApp(eventLoopGroup: eventLoopGroup, repository: repository, path: path, postsPerPage: postsPerPage, feedInformation: feedInformation, blogPresenter: blogPresenter, adminPresenter: blogAdminPresenter, enableAuthorPages: enableAuthorPages, enableTagPages: enableTagPages, passwordHasherToUse: passwordHasherToUse, randomNumberGenerator: randomNumberGenerator) + let application = TestWorld.getSteamPressApp(eventLoopGroup: eventLoopGroup, repository: repository, path: path, postsPerPage: postsPerPage, feedInformation: feedInformation, blogPresenter: blogPresenter, adminPresenter: blogAdminPresenter, enableAuthorPages: enableAuthorPages, enableTagPages: enableTagPages, passwordHasherToUse: passwordHasherToUse, randomNumberGenerator: randomNumberGenerator) let context = Context(app: application, repository: repository, blogPresenter: blogPresenter, blogAdminPresenter: blogAdminPresenter, path: path, eventLoopGroup: eventLoopGroup) unsetenv("BLOG_GOOGLE_ANALYTICS_IDENTIFIER") unsetenv("BLOG_SITE_TWITTER_HANDLE") unsetenv("BLOG_DISQUS_NAME") unsetenv("WEBSITE_URL") - #warning("When do we do this?") - try application.boot() +// #warning("When do we do this?") +// try application.boot() return TestWorld(context: context) } From 211e1389cac4912df0f90c6a80169fa448271799 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Mon, 6 Apr 2020 13:36:37 +0100 Subject: [PATCH 48/70] Stop the tests from crashing --- .../Extensions/BCrypt+PasswordHasher.swift | 2 +- .../APITests/APITagControllerTests.swift | 17 +++++++++++++---- .../AdminTests/AdminPostTests.swift | 1 + .../AdminTests/AdminUserTests.swift | 3 +++ .../SteamPressTests/AdminTests/LoginTests.swift | 1 + .../SteamPressTests/BlogTests/AuthorTests.swift | 1 + .../SteamPressTests/BlogTests/IndexTests.swift | 2 ++ 7 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift index b5b84767..884aad6a 100644 --- a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift +++ b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift @@ -8,7 +8,7 @@ public protocol PasswordHasher { extension BCryptDigest: PasswordHasher { public func hash(_ plaintext: String) throws -> String { - return try self.hash(plaintext) + return try self.hash(plaintext, cost: 12) } public func `for`(_ request: Request) -> PasswordHasher { diff --git a/Tests/SteamPressTests/APITests/APITagControllerTests.swift b/Tests/SteamPressTests/APITests/APITagControllerTests.swift index 0e0ee499..3e28db15 100644 --- a/Tests/SteamPressTests/APITests/APITagControllerTests.swift +++ b/Tests/SteamPressTests/APITests/APITagControllerTests.swift @@ -4,11 +4,22 @@ import SteamPress class APITagControllerTests: XCTestCase { + // MARK: - Properties + var testWorld: TestWorld! + + // MARK: - Overrides + + override func setUp() { + testWorld = TestWorld.create() + } + + override func tearDownWithError() throws { + try testWorld.shutdown() + } + // MARK: - Tests func testThatAllTagsAreReturnedFromAPI() throws { - let testWorld = TestWorld.create() - let tag1 = try testWorld.context.repository.addTag(name: "Vapor3") let tag2 = try testWorld.context.repository.addTag(name: "Engineering") @@ -16,8 +27,6 @@ class APITagControllerTests: XCTestCase { XCTAssertEqual(tags[0].name, tag1.name) XCTAssertEqual(tags[1].name, tag2.name) - - XCTAssertNoThrow(try testWorld.shutdown()) } } diff --git a/Tests/SteamPressTests/AdminTests/AdminPostTests.swift b/Tests/SteamPressTests/AdminTests/AdminPostTests.swift index e298f4bd..93b497d8 100644 --- a/Tests/SteamPressTests/AdminTests/AdminPostTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminPostTests.swift @@ -59,6 +59,7 @@ class AdminPostTests: XCTestCase { func testCreatingPostWithNonUniqueSlugFromSameTitle() throws { let randomNumber = 345 + try testWorld.shutdown() testWorld = TestWorld.create(randomNumberGenerator: StubbedRandomNumberGenerator(numberToReturn: randomNumber)) let initialPostData = try testWorld.createPost(title: "Post Title", slugUrl: "post-title") diff --git a/Tests/SteamPressTests/AdminTests/AdminUserTests.swift b/Tests/SteamPressTests/AdminTests/AdminUserTests.swift index fe22289c..6aa9058c 100644 --- a/Tests/SteamPressTests/AdminTests/AdminUserTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminUserTests.swift @@ -348,6 +348,7 @@ class AdminUserTests: XCTestCase { } func testPasswordIsActuallyHashedWhenCreatingAUser() throws { + try testWorld.shutdown() testWorld = TestWorld.create(passwordHasherToUse: .reversed) let usersPassword = "password" let hashedPassword = String(usersPassword.reversed()) @@ -637,6 +638,7 @@ class AdminUserTests: XCTestCase { } func testPasswordIsActuallyHashedWhenEditingAUser() throws { + try testWorld.shutdown() testWorld = TestWorld.create(passwordHasherToUse: .reversed) let usersPassword = "password" let hashedPassword = String(usersPassword.reversed()) @@ -723,6 +725,7 @@ class AdminUserTests: XCTestCase { } func testCannotDeleteLastUser() throws { + try testWorld.shutdown() testWorld = TestWorld.create() let adminUser = testWorld.createUser(name: "Admin", username: "admin") let testData = try testWorld.createPost(author: adminUser) diff --git a/Tests/SteamPressTests/AdminTests/LoginTests.swift b/Tests/SteamPressTests/AdminTests/LoginTests.swift index b03168f7..920f6996 100644 --- a/Tests/SteamPressTests/AdminTests/LoginTests.swift +++ b/Tests/SteamPressTests/AdminTests/LoginTests.swift @@ -32,6 +32,7 @@ class LoginTests: XCTestCase { // MARK: - Tests func testLogin() throws { + try testWorld.shutdown() testWorld = TestWorld.create(path: "blog", passwordHasherToUse: .real) let hashedPassword = try BCryptDigest().hash("password") user = testWorld.createUser(password: hashedPassword) diff --git a/Tests/SteamPressTests/BlogTests/AuthorTests.swift b/Tests/SteamPressTests/BlogTests/AuthorTests.swift index 00b314b9..b0ed6227 100644 --- a/Tests/SteamPressTests/BlogTests/AuthorTests.swift +++ b/Tests/SteamPressTests/BlogTests/AuthorTests.swift @@ -53,6 +53,7 @@ class AuthorTests: XCTestCase { } func testDisabledBlogAuthorsPath() throws { + try testWorld.shutdown() testWorld = TestWorld.create(enableAuthorPages: false) _ = testWorld.createUser(username: "leia") diff --git a/Tests/SteamPressTests/BlogTests/IndexTests.swift b/Tests/SteamPressTests/BlogTests/IndexTests.swift index b581c1c7..48907191 100644 --- a/Tests/SteamPressTests/BlogTests/IndexTests.swift +++ b/Tests/SteamPressTests/BlogTests/IndexTests.swift @@ -76,6 +76,7 @@ class IndexTests: XCTestCase { } func testThatAccessingPathsRouteRedirectsToBlogIndexWithCustomPath() throws { + try testWorld.shutdown() testWorld = TestWorld.create(path: "blog") let response = try testWorld.getResponse(to: "/blog/posts/") XCTAssertEqual(response.status, .movedPermanently) @@ -135,6 +136,7 @@ class IndexTests: XCTestCase { } func testIndexPageCurrentPageWhenAtSubPath() throws { + try testWorld.shutdown() testWorld = TestWorld.create(path: "blog") _ = try testWorld.getResponse(to: "/blog") XCTAssertEqual(presenter.indexPageInformation?.currentPageURL.absoluteString, "/blog") From 80c3034cc22528e2a0415bca8d55c48e90b3085d Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Mon, 6 Apr 2020 13:49:14 +0100 Subject: [PATCH 49/70] Get some more of the tests passing --- Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift | 6 +----- Tests/SteamPressTests/Helpers/TestWorld.swift | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift index 884aad6a..47a0e50f 100644 --- a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift +++ b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift @@ -21,11 +21,7 @@ public protocol SteamPressPasswordVerifier { func verify(_ plaintext: String, created hash: String) throws -> Bool } -extension BCryptDigest: SteamPressPasswordVerifier { - public func verify(_ plaintext: String, created hash: String) throws -> Bool { - return try self.verify(plaintext, created: hash) - } - +extension BCryptDigest: SteamPressPasswordVerifier { public func `for`(_ request: Request) -> SteamPressPasswordVerifier { return BCryptDigest() } diff --git a/Tests/SteamPressTests/Helpers/TestWorld.swift b/Tests/SteamPressTests/Helpers/TestWorld.swift index ed1611a9..6c35f39d 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld.swift @@ -14,8 +14,7 @@ struct TestWorld { unsetenv("BLOG_SITE_TWITTER_HANDLE") unsetenv("BLOG_DISQUS_NAME") unsetenv("WEBSITE_URL") -// #warning("When do we do this?") -// try application.boot() + try! application.boot() return TestWorld(context: context) } From a0ac133bde973af6ccb6253123ed39b6832d0dc0 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Mon, 6 Apr 2020 13:50:52 +0100 Subject: [PATCH 50/70] Set WEBSITE_URL in setup as it's now required --- Tests/SteamPressTests/Helpers/TestWorld.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/SteamPressTests/Helpers/TestWorld.swift b/Tests/SteamPressTests/Helpers/TestWorld.swift index 6c35f39d..0ee5c935 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld.swift @@ -3,7 +3,7 @@ import Vapor struct TestWorld { - static func create(path: String? = nil, postsPerPage: Int = 10, feedInformation: FeedInformation = FeedInformation(), enableAuthorPages: Bool = true, enableTagPages: Bool = true, passwordHasherToUse: PasswordHasherChoice = .plaintext, randomNumberGenerator: StubbedRandomNumberGenerator = StubbedRandomNumberGenerator(numberToReturn: 666)) -> TestWorld { + static func create(path: String? = nil, postsPerPage: Int = 10, feedInformation: FeedInformation = FeedInformation(), enableAuthorPages: Bool = true, enableTagPages: Bool = true, passwordHasherToUse: PasswordHasherChoice = .plaintext, randomNumberGenerator: StubbedRandomNumberGenerator = StubbedRandomNumberGenerator(numberToReturn: 666), websiteURL: String = "https://www.steampress.io") -> TestWorld { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) let repository = InMemoryRepository(eventLoop: eventLoopGroup.next()) let blogPresenter = CapturingBlogPresenter(eventLoop: eventLoopGroup.next()) @@ -14,6 +14,7 @@ struct TestWorld { unsetenv("BLOG_SITE_TWITTER_HANDLE") unsetenv("BLOG_DISQUS_NAME") unsetenv("WEBSITE_URL") + setenv("WEBSITE_URL", websiteURL, 1) try! application.boot() return TestWorld(context: context) } From ebbaa4cd18a54f4d8cd1def716ce56181b2e6034 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Mon, 6 Apr 2020 14:49:32 +0100 Subject: [PATCH 51/70] Return the same instance of the presenter in test --- .../Extensions/BCrypt+PasswordHasher.swift | 15 ++++++------- .../Presenters/BlogAdminPresenter.swift | 6 ++--- .../SteamPress/Presenters/BlogPresenter.swift | 22 +++++++++---------- .../Repositories/SteamPressRepository.swift | 6 ++--- .../SteamPressRandomNumberGenerator.swift | 6 ++--- Sources/SteamPress/SteampressLifecyle.swift | 14 +----------- .../AdminTests/LoginTests.swift | 2 +- .../Presenters/CapturingAdminPresenter.swift | 16 +------------- .../Presenters/CapturingBlogPresenter.swift | 18 ++------------- .../Helpers/TestWorld+Application.swift | 10 ++++++--- 10 files changed, 39 insertions(+), 76 deletions(-) diff --git a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift index 47a0e50f..e501095f 100644 --- a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift +++ b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift @@ -21,13 +21,12 @@ public protocol SteamPressPasswordVerifier { func verify(_ plaintext: String, created hash: String) throws -> Bool } -extension BCryptDigest: SteamPressPasswordVerifier { +extension BCryptDigest: SteamPressPasswordVerifier { public func `for`(_ request: Request) -> SteamPressPasswordVerifier { return BCryptDigest() } } - public extension Request { var passwordHasher: PasswordHasher { self.application.passwordHashers.passwordHasher.for(self) @@ -90,10 +89,10 @@ public extension Application { } private var storage: Storage { - guard let storage = self.application.storage[Key.self] else { - fatalError("PasswordVerifiers not configured. Configure with app.passwordVerifiers.initialize()") + if self.application.storage[Key.self] == nil { + self.initialize() } - return storage + return self.application.storage[Key.self]! } } @@ -152,10 +151,10 @@ public extension Application { } private var storage: Storage { - guard let storage = self.application.storage[Key.self] else { - fatalError("PasswordHashers not configured. Configure with app.passwordHashers.initialize()") + if self.application.storage[Key.self] == nil { + self.initialize() } - return storage + return self.application.storage[Key.self]! } } diff --git a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift index 6458ddc8..b603f03d 100644 --- a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift @@ -20,9 +20,9 @@ extension Request { } } -public extension Application { +extension Application { struct BlogAdminPresenters { - public struct Provider { + struct Provider { static var view: Self { .init { $0.adminPresenters.use { $0.adminPresenters.view } @@ -61,7 +61,7 @@ public extension Application { return makePresenter(self.application) } - public func use(_ provider: Provider) { + func use(_ provider: Provider) { provider.run(self.application) } diff --git a/Sources/SteamPress/Presenters/BlogPresenter.swift b/Sources/SteamPress/Presenters/BlogPresenter.swift index ffd0d3cc..91698857 100644 --- a/Sources/SteamPress/Presenters/BlogPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogPresenter.swift @@ -1,6 +1,6 @@ import Vapor -public protocol BlogPresenter { +protocol BlogPresenter { func `for`(_ request: Request) -> BlogPresenter func indexView(posts: [BlogPost], tags: [BlogTag], authors: [BlogUser], tagsForPosts: [Int: [BlogTag]], pageInformation: BlogGlobalPageInformation, paginationTagInfo: PaginationTagInformation) -> EventLoopFuture func postView(post: BlogPost, author: BlogUser, tags: [BlogTag], pageInformation: BlogGlobalPageInformation) -> EventLoopFuture @@ -13,20 +13,20 @@ public protocol BlogPresenter { } extension ViewBlogPresenter { - public func `for`(_ request: Request) -> BlogPresenter { + func `for`(_ request: Request) -> BlogPresenter { return ViewBlogPresenter(viewRenderer: request.view, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: request.eventLoop) } } -public extension Request { +extension Request { var blogPresenter: BlogPresenter { self.application.blogPresenters.blogPresenter.for(self) } } -public extension Application { +extension Application { struct BlogPresenters { - public struct Provider { + struct Provider { static var view: Self { .init { $0.blogPresenters.use { $0.blogPresenters.view } @@ -49,7 +49,7 @@ public extension Application { typealias Value = Storage } - public let application: Application + let application: Application var view: ViewBlogPresenter { return .init(viewRenderer: self.application.views.renderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: self.application.eventLoopGroup) @@ -62,11 +62,11 @@ public extension Application { return makePresenter(self.application) } - public func use(_ provider: Provider) { + func use(_ provider: Provider) { provider.run(self.application) } - public func use(_ makePresenter: @escaping (Application) -> (BlogPresenter)) { + func use(_ makePresenter: @escaping (Application) -> (BlogPresenter)) { self.storage.makePresenter = makePresenter } @@ -76,10 +76,10 @@ public extension Application { } private var storage: Storage { - guard let storage = self.application.storage[Key.self] else { - fatalError("BlogPresenters not configured. Configure with app.blogPresenters.initialize()") + if self.application.storage[Key.self] == nil { + self.initialize() } - return storage + return self.application.storage[Key.self]! } } diff --git a/Sources/SteamPress/Repositories/SteamPressRepository.swift b/Sources/SteamPress/Repositories/SteamPressRepository.swift index 737ce667..90ee244a 100644 --- a/Sources/SteamPress/Repositories/SteamPressRepository.swift +++ b/Sources/SteamPress/Repositories/SteamPressRepository.swift @@ -121,10 +121,10 @@ public extension Application { } private var storage: Storage { - guard let storage = self.application.storage[Key.self] else { - fatalError("Repositoroes not configured. Configure with app.blogRepositories.initialize()") + if self.application.storage[Key.self] == nil { + self.initialize() } - return storage + return self.application.storage[Key.self]! } } diff --git a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift index 4accf512..7d75bdb0 100644 --- a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift +++ b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift @@ -69,10 +69,10 @@ public extension Application { } private var storage: Storage { - guard let storage = self.application.storage[Key.self] else { - fatalError("RandomNumberGenerators not configured. Configure with app.randomNumberGenerators.initialize()") + if self.application.storage[Key.self] == nil { + self.initialize() } - return storage + return self.application.storage[Key.self]! } } diff --git a/Sources/SteamPress/SteampressLifecyle.swift b/Sources/SteamPress/SteampressLifecyle.swift index 97e0286d..09eb7d4f 100644 --- a/Sources/SteamPress/SteampressLifecyle.swift +++ b/Sources/SteamPress/SteampressLifecyle.swift @@ -37,22 +37,10 @@ public struct SteampressLifecyle: LifecycleHandler { } public func tmpSetup(_ application: Application) { - application.randomNumberGenerators.initialize() - application.blogPresenters.initialize() application.adminPresenters.initialize(pathCreator: pathCreator) - application.passwordHashers.initialize() - application.passwordVerifiers.initialize() - application.blogRepositories.initialize() } - public func willBoot(_ application: Application) throws { -// application.randomNumberGenerators.initialize() -// application.blogPresenters.initialize() -// application.adminPresenters.initialize(pathCreator: pathCreator) -// application.passwordHashers.initialize() -// application.passwordVerifiers.initialize() -// application.blogRepositories.initialize() - + public func willBoot(_ application: Application) throws { let router = application.routes let feedController = FeedController(pathCreator: self.pathCreator, feedInformation: self.feedInformation) diff --git a/Tests/SteamPressTests/AdminTests/LoginTests.swift b/Tests/SteamPressTests/AdminTests/LoginTests.swift index 920f6996..8731765d 100644 --- a/Tests/SteamPressTests/AdminTests/LoginTests.swift +++ b/Tests/SteamPressTests/AdminTests/LoginTests.swift @@ -79,7 +79,7 @@ class LoginTests: XCTestCase { } func testPresenterGetsCorrectInformationForResetPasswordPage() throws { - _ = try testWorld.getResponse(to: "/blog/admin/resetPassword", loggedInUser: user) + let response = try testWorld.getResponse(to: "/blog/admin/resetPassword", loggedInUser: user) XCTAssertNil(presenter.resetPasswordErrors) XCTAssertNil(presenter.resetPasswordError) XCTAssertNil(presenter.resetPasswordConfirmError) diff --git a/Tests/SteamPressTests/Fakes/Presenters/CapturingAdminPresenter.swift b/Tests/SteamPressTests/Fakes/Presenters/CapturingAdminPresenter.swift index 5890dbaa..c63b66d8 100644 --- a/Tests/SteamPressTests/Fakes/Presenters/CapturingAdminPresenter.swift +++ b/Tests/SteamPressTests/Fakes/Presenters/CapturingAdminPresenter.swift @@ -9,7 +9,7 @@ class CapturingAdminPresenter: BlogAdminPresenter { } func `for`(_ request: Request, pathCreator: BlogPathCreator) -> BlogAdminPresenter { - return CapturingAdminPresenter(eventLoop: request.eventLoop) + return self } // MARK: - BlogPresenter @@ -95,17 +95,3 @@ class CapturingAdminPresenter: BlogAdminPresenter { return TestDataBuilder.createFutureView(on: eventLoop) } } - -extension Application.BlogAdminPresenters { - var capturing: CapturingAdminPresenter { - return .init(eventLoop: self.application.eventLoopGroup.next()) - } -} - -extension Application.BlogAdminPresenters.Provider { - static var capturing: Self { - .init { - $0.adminPresenters.use { $0.adminPresenters.capturing } - } - } -} diff --git a/Tests/SteamPressTests/Fakes/Presenters/CapturingBlogPresenter.swift b/Tests/SteamPressTests/Fakes/Presenters/CapturingBlogPresenter.swift index b618d4a3..cc36e8fb 100644 --- a/Tests/SteamPressTests/Fakes/Presenters/CapturingBlogPresenter.swift +++ b/Tests/SteamPressTests/Fakes/Presenters/CapturingBlogPresenter.swift @@ -1,4 +1,4 @@ -import SteamPress +@testable import SteamPress import Vapor import Foundation @@ -11,7 +11,7 @@ class CapturingBlogPresenter: BlogPresenter { } func `for`(_ request: Request) -> BlogPresenter { - return CapturingBlogPresenter(eventLoop: request.eventLoop) + return self } // MARK: - BlogPresenter @@ -131,17 +131,3 @@ class CapturingBlogPresenter: BlogPresenter { return TestDataBuilder.createFutureView(on: eventLoop) } } - -extension Application.BlogPresenters { - var capturing: CapturingBlogPresenter { - return .init(eventLoop: self.application.eventLoopGroup.next()) - } -} - -extension Application.BlogPresenters.Provider { - static var capturing: Self { - .init { - $0.blogPresenters.use { $0.blogPresenters.capturing } - } - } -} diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift index 1c6087b2..3fa0ab09 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift @@ -1,4 +1,4 @@ -import SteamPress +@testable import SteamPress import Vapor extension TestWorld { @@ -35,8 +35,12 @@ extension TestWorld { application.middleware.use(BlogRememberMeMiddleware()) application.middleware.use(SessionsMiddleware(session: application.sessions.driver)) - application.blogPresenters.use(.capturing) - application.adminPresenters.use(.capturing) + application.blogPresenters.use { _ in + return blogPresenter + } + application.adminPresenters.use { _ in + return adminPresenter + } switch passwordHasherToUse { case .real: From 00837e47f355cd8ed6f07cb0f44e540193ba8269 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Mon, 6 Apr 2020 15:00:25 +0100 Subject: [PATCH 52/70] Fix tag and user paths --- Sources/SteamPress/Extensions/Models+Parameters.swift | 4 ++-- Sources/SteamPress/Extensions/URL+Converters.swift | 9 ++++++++- Tests/SteamPressTests/AdminTests/AdminPageTests.swift | 4 ++-- Tests/SteamPressTests/AdminTests/AdminPostTests.swift | 6 +++--- Tests/SteamPressTests/AdminTests/LoginTests.swift | 4 ++-- Tests/SteamPressTests/BlogTests/AuthorTests.swift | 2 +- Tests/SteamPressTests/BlogTests/IndexTests.swift | 4 ++-- Tests/SteamPressTests/BlogTests/PostTests.swift | 2 +- Tests/SteamPressTests/BlogTests/SearchTests.swift | 2 +- 9 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Sources/SteamPress/Extensions/Models+Parameters.swift b/Sources/SteamPress/Extensions/Models+Parameters.swift index 72b3cea5..d83ab769 100644 --- a/Sources/SteamPress/Extensions/Models+Parameters.swift +++ b/Sources/SteamPress/Extensions/Models+Parameters.swift @@ -3,7 +3,7 @@ import Vapor extension BlogUser: ParameterModel { // typealias Repository = BlogUserRepository public static let parameterKey = "blogUserID" - public static let parameter = PathComponent(stringLiteral: ":\(BlogPost.parameterKey)") + public static let parameter = PathComponent(stringLiteral: ":\(BlogUser.parameterKey)") // public typealias ResolvedParameter = EventLoopFuture // public static func resolveParameter(_ parameter: String, on container: Container) throws -> BlogUser.ResolvedParameter { @@ -33,7 +33,7 @@ extension BlogPost: ParameterModel { extension BlogTag: ParameterModel { // typealias Repository = BlogTagRepository public static let parameterKey = "blogTagName" - public static let parameter = PathComponent(stringLiteral: ":\(BlogPost.parameterKey)") + public static let parameter = PathComponent(stringLiteral: ":\(BlogTag.parameterKey)") // public typealias ResolvedParameter = EventLoopFuture // public static func resolveParameter(_ parameter: String, on container: Container) throws -> EventLoopFuture { diff --git a/Sources/SteamPress/Extensions/URL+Converters.swift b/Sources/SteamPress/Extensions/URL+Converters.swift index 49f91b62..c82774d5 100644 --- a/Sources/SteamPress/Extensions/URL+Converters.swift +++ b/Sources/SteamPress/Extensions/URL+Converters.swift @@ -5,7 +5,14 @@ extension Request { func url() throws -> URL { let path = self.url.path.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" let rootURL = try self.rootUrl() - return rootURL.appendingPathComponent(path) + if rootURL.absoluteString == "/" { + guard let pathURL = URL(string: path) else { + throw SteamPressError(identifier: "SteamPressError", "Failed to convert path to URL") + } + return pathURL + } else { + return rootURL.appendingPathComponent(path) + } } func rootUrl() throws -> URL { diff --git a/Tests/SteamPressTests/AdminTests/AdminPageTests.swift b/Tests/SteamPressTests/AdminTests/AdminPageTests.swift index d41f2024..28d9086c 100644 --- a/Tests/SteamPressTests/AdminTests/AdminPageTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminPageTests.swift @@ -5,7 +5,7 @@ import SteamPress class AdminPageTests: XCTestCase { func testAdminPagePassesCorrectInformationToPresenter() throws { - let testWorld = TestWorld.create() + let testWorld = TestWorld.create(websiteURL: "/") let user = testWorld.createUser(username: "leia") let testData1 = try testWorld.createPost(author: user) let testData2 = try testWorld.createPost(title: "A second post", author: user) @@ -22,7 +22,7 @@ class AdminPageTests: XCTestCase { XCTAssertEqual(presenter.adminViewPageInformation?.loggedInUser.username, user.username) XCTAssertEqual(presenter.adminViewPageInformation?.websiteURL.absoluteString, "/") - XCTAssertEqual(presenter.adminViewPageInformation?.currentPageURL.absoluteString, "/admin") + XCTAssertEqual(presenter.adminViewPageInformation?.currentPageURL.absoluteString, "/admin/") XCTAssertNoThrow(try testWorld.shutdown()) } diff --git a/Tests/SteamPressTests/AdminTests/AdminPostTests.swift b/Tests/SteamPressTests/AdminTests/AdminPostTests.swift index 93b497d8..dd87d9b9 100644 --- a/Tests/SteamPressTests/AdminTests/AdminPostTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminPostTests.swift @@ -16,7 +16,7 @@ class AdminPostTests: XCTestCase { // MARK: - Overrides override func setUp() { - testWorld = TestWorld.create() + testWorld = TestWorld.create(websiteURL: "/") user = testWorld.createUser(username: "leia") } @@ -96,7 +96,7 @@ class AdminPostTests: XCTestCase { XCTAssertFalse(titleError) XCTAssertFalse(contentsError) XCTAssertEqual(presenter.createPostPageInformation?.loggedInUser.username, user.username) - XCTAssertEqual(presenter.createPostPageInformation?.currentPageURL.absoluteString, "/admin/createPost") + XCTAssertEqual(presenter.createPostPageInformation?.currentPageURL.absoluteString, "/admin/createPost/") XCTAssertEqual(presenter.createPostPageInformation?.websiteURL.absoluteString, "/") } @@ -131,7 +131,7 @@ class AdminPostTests: XCTestCase { XCTAssertTrue(titleError) XCTAssertFalse(contentsError) XCTAssertEqual(presenter.createPostPageInformation?.loggedInUser.username, user.username) - XCTAssertEqual(presenter.createPostPageInformation?.currentPageURL.absoluteString, "/admin/createPost") + XCTAssertEqual(presenter.createPostPageInformation?.currentPageURL.absoluteString, "/admin/createPost/") XCTAssertEqual(presenter.createPostPageInformation?.websiteURL.absoluteString, "/") } diff --git a/Tests/SteamPressTests/AdminTests/LoginTests.swift b/Tests/SteamPressTests/AdminTests/LoginTests.swift index 8731765d..744c261b 100644 --- a/Tests/SteamPressTests/AdminTests/LoginTests.swift +++ b/Tests/SteamPressTests/AdminTests/LoginTests.swift @@ -21,7 +21,7 @@ class LoginTests: XCTestCase { // MARK: - Overrides override func setUp() { - testWorld = TestWorld.create(path: "blog") + testWorld = TestWorld.create(path: "blog", websiteURL: "/") user = testWorld.createUser() } @@ -79,7 +79,7 @@ class LoginTests: XCTestCase { } func testPresenterGetsCorrectInformationForResetPasswordPage() throws { - let response = try testWorld.getResponse(to: "/blog/admin/resetPassword", loggedInUser: user) + _ = try testWorld.getResponse(to: "/blog/admin/resetPassword", loggedInUser: user) XCTAssertNil(presenter.resetPasswordErrors) XCTAssertNil(presenter.resetPasswordError) XCTAssertNil(presenter.resetPasswordConfirmError) diff --git a/Tests/SteamPressTests/BlogTests/AuthorTests.swift b/Tests/SteamPressTests/BlogTests/AuthorTests.swift index b0ed6227..a0914364 100644 --- a/Tests/SteamPressTests/BlogTests/AuthorTests.swift +++ b/Tests/SteamPressTests/BlogTests/AuthorTests.swift @@ -19,7 +19,7 @@ class AuthorTests: XCTestCase { // MARK: - Overrides override func setUp() { - testWorld = TestWorld.create(postsPerPage: postsPerPage) + testWorld = TestWorld.create(postsPerPage: postsPerPage, websiteURL: "/") user = testWorld.createUser(username: "leia") postData = try! testWorld.createPost(author: user) } diff --git a/Tests/SteamPressTests/BlogTests/IndexTests.swift b/Tests/SteamPressTests/BlogTests/IndexTests.swift index 48907191..be56fd48 100644 --- a/Tests/SteamPressTests/BlogTests/IndexTests.swift +++ b/Tests/SteamPressTests/BlogTests/IndexTests.swift @@ -17,7 +17,7 @@ class IndexTests: XCTestCase { // MARK: - Overrides override func setUp() { - testWorld = TestWorld.create(postsPerPage: postsPerPage) + testWorld = TestWorld.create(postsPerPage: postsPerPage, websiteURL: "/") firstData = try! testWorld.createPost(title: "Test Path", slugUrl: "test-path") } @@ -137,7 +137,7 @@ class IndexTests: XCTestCase { func testIndexPageCurrentPageWhenAtSubPath() throws { try testWorld.shutdown() - testWorld = TestWorld.create(path: "blog") + testWorld = TestWorld.create(path: "blog", websiteURL: "/") _ = try testWorld.getResponse(to: "/blog") XCTAssertEqual(presenter.indexPageInformation?.currentPageURL.absoluteString, "/blog") XCTAssertEqual(presenter.indexPageInformation?.websiteURL.absoluteString, "/") diff --git a/Tests/SteamPressTests/BlogTests/PostTests.swift b/Tests/SteamPressTests/BlogTests/PostTests.swift index dd51c42f..458c2541 100644 --- a/Tests/SteamPressTests/BlogTests/PostTests.swift +++ b/Tests/SteamPressTests/BlogTests/PostTests.swift @@ -17,7 +17,7 @@ class PostTests: XCTestCase { // MARK: - Overrides override func setUpWithError() throws { - testWorld = TestWorld.create() + testWorld = TestWorld.create(websiteURL: "/") firstData = try testWorld.createPost(title: "Test Path", slugUrl: "test-path") } diff --git a/Tests/SteamPressTests/BlogTests/SearchTests.swift b/Tests/SteamPressTests/BlogTests/SearchTests.swift index c431a91b..c2d0dc55 100644 --- a/Tests/SteamPressTests/BlogTests/SearchTests.swift +++ b/Tests/SteamPressTests/BlogTests/SearchTests.swift @@ -16,7 +16,7 @@ class SearchTests: XCTestCase { // MARK: - Overrides override func setUpWithError() throws { - testWorld = TestWorld.create() + testWorld = TestWorld.create(websiteURL: "/") firstData = try testWorld.createPost(title: "Test Path", slugUrl: "test-path") } From 0ce517e896a7769389568dbf82b745785f788161 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Mon, 6 Apr 2020 15:00:56 +0100 Subject: [PATCH 53/70] Fix the last tag tests --- Tests/SteamPressTests/BlogTests/TagTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SteamPressTests/BlogTests/TagTests.swift b/Tests/SteamPressTests/BlogTests/TagTests.swift index 6c8cf4c8..78df2f83 100644 --- a/Tests/SteamPressTests/BlogTests/TagTests.swift +++ b/Tests/SteamPressTests/BlogTests/TagTests.swift @@ -20,7 +20,7 @@ class TagTests: XCTestCase { // MARK: - Overrides override func setUp() { - testWorld = TestWorld.create(postsPerPage: postsPerPage) + testWorld = TestWorld.create(postsPerPage: postsPerPage, websiteURL: "/") postData = try! testWorld.createPost() tag = try! testWorld.createTag(tagName, on: postData.post) } From 0c9a6783e2e378b6ebc29bd6020cdb0e4a086202 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Mon, 6 Apr 2020 15:35:06 +0100 Subject: [PATCH 54/70] Migrate the Atom tests --- .../Feed Tests/AtomFeedTests.swift | 66 ++++--------------- 1 file changed, 12 insertions(+), 54 deletions(-) diff --git a/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift b/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift index d5aec9e2..99202d04 100644 --- a/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift +++ b/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift @@ -27,7 +27,7 @@ class AtomFeedTests: XCTestCase { func testNoPostsReturnsCorrectAtomFeed() throws { testWorld = TestWorld.create() - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" + let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) @@ -39,7 +39,7 @@ class AtomFeedTests: XCTestCase { let feedInformation = FeedInformation(title: title) testWorld = TestWorld.create(feedInformation: feedInformation) - let expectedXML = "\n\n\n\(title)\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" + let expectedXML = "\n\n\n\(title)\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -50,7 +50,7 @@ class AtomFeedTests: XCTestCase { let feedInformation = FeedInformation(description: description) testWorld = TestWorld.create(feedInformation: feedInformation) - let expectedXML = "\n\n\nSteamPress Blog\n\(description)\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" + let expectedXML = "\n\n\nSteamPress Blog\n\(description)\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -61,41 +61,17 @@ class AtomFeedTests: XCTestCase { let feedInformation = FeedInformation(copyright: copyright) testWorld = TestWorld.create(feedInformation: feedInformation) - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\(copyright)\n" + let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\(copyright)\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) XCTAssertEqual(actualXmlResponse, expectedXML) } - func testThatLinksAreCorrectForFullURI() throws { - testWorld = TestWorld.create(path: "blog") - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://geeks.brokenhands.io/blog/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" - - let fullPath = "/blog/atom.xml" - let actualXmlResponse = try testWorld.getResponseString(to: fullPath, headers: [ - "X-Forwarded-Proto": "https", - "X-Forwarded-For": "geeks.brokenhands.io" - ]) - XCTAssertEqual(actualXmlResponse, expectedXML) - } - - func testThatHTTPSLinksWorkWhenBehindReverseProxy() throws { - testWorld = TestWorld.create() - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://geeks.brokenhands.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" - - let fullPath = "/atom.xml" - let actualXmlResponse = try testWorld.getResponseString(to: fullPath, headers: [ - "X-Forwarded-Proto": "https", - "X-Forwarded-For": "geeks.brokenhands.io" - ]) - XCTAssertEqual(actualXmlResponse, expectedXML) - } - func testThatLogoCanBeConfigured() throws { let imageURL = "https://static.brokenhands.io/images/feeds/atom.png" let feedInformation = FeedInformation(imageURL: imageURL) testWorld = TestWorld.create(feedInformation: feedInformation) - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\(imageURL)\n" + let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\(imageURL)\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -108,7 +84,7 @@ class AtomFeedTests: XCTestCase { let post = testData.post let author = testData.author - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(self.dateFormatter.string(from: Date()))\n\n/posts-id/1/\n\(post.title)\n\(self.dateFormatter.string(from: post.created))\n\(self.dateFormatter.string(from: post.created))\n\n\(author.name)\n/authors/\(author.username)/\n\n\(try post.description())\n\n\n" + let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(self.dateFormatter.string(from: Date()))\n\nhttps://www.steampress.io/posts-id/1/\n\(post.title)\n\(self.dateFormatter.string(from: post.created))\n\(self.dateFormatter.string(from: post.created))\n\n\(author.name)\nhttps://www.steampress.io/authors/\(author.username)/\n\n\(try post.description())\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -121,7 +97,7 @@ class AtomFeedTests: XCTestCase { let post = testData.post let author = testData.author - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/blog/\n\n\nSteamPress\n\(self.dateFormatter.string(from: Date()))\n\n/blog/posts-id/1/\n\(post.title)\n\(self.dateFormatter.string(from: post.created))\n\(self.dateFormatter.string(from: post.created))\n\n\(author.name)\n/blog/authors/\(author.username)/\n\n\(try post.description())\n\n\n" + let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/blog/\n\n\nSteamPress\n\(self.dateFormatter.string(from: Date()))\n\nhttps://www.steampress.io/blog/posts-id/1/\n\(post.title)\n\(self.dateFormatter.string(from: post.created))\n\(self.dateFormatter.string(from: post.created))\n\n\(author.name)\nhttps://www.steampress.io/blog/authors/\(author.username)/\n\n\(try post.description())\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: blogAtomPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -138,7 +114,7 @@ class AtomFeedTests: XCTestCase { let secondPostDate = Date() let post2 = try testWorld.createPost(createdDate: secondPostDate, title: secondTitle, author: author).post - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\n/posts-id/2/\n\(secondTitle)\n\(dateFormatter.string(from: secondPostDate))\n\(dateFormatter.string(from: secondPostDate))\n\n\(author.name)\n/authors/\(author.username)/\n\n\(try post2.description())\n\n\n\n/posts-id/1/\n\(post.title)\n\(dateFormatter.string(from: post.created))\n\(dateFormatter.string(from: post.created))\n\n\(author.name)\n/authors/\(author.username)/\n\n\(try post.description())\n\n\n" + let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\nhttps://www.steampress.io/posts-id/2/\n\(secondTitle)\n\(dateFormatter.string(from: secondPostDate))\n\(dateFormatter.string(from: secondPostDate))\n\n\(author.name)\nhttps://www.steampress.io/authors/\(author.username)/\n\n\(try post2.description())\n\n\n\nhttps://www.steampress.io/posts-id/1/\n\(post.title)\n\(dateFormatter.string(from: post.created))\n\(dateFormatter.string(from: post.created))\n\n\(author.name)\nhttps://www.steampress.io/authors/\(author.username)/\n\n\(try post.description())\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -153,7 +129,7 @@ class AtomFeedTests: XCTestCase { _ = try testWorld.createPost(title: "A Draft Post", published: false) - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\n/posts-id/1/\n\(post.title)\n\(dateFormatter.string(from: post.created))\n\(dateFormatter.string(from: post.created))\n\n\(author.name)\n/authors/\(author.username)/\n\n\(try post.description())\n\n\n" + let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\nhttps://www.steampress.io/posts-id/1/\n\(post.title)\n\(dateFormatter.string(from: post.created))\n\(dateFormatter.string(from: post.created))\n\n\(author.name)\nhttps://www.steampress.io/authors/\(author.username)/\n\n\(try post.description())\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -173,7 +149,7 @@ class AtomFeedTests: XCTestCase { let post2 = try testWorld.createPost(createdDate: secondPostDate, title: secondTitle, author: author).post post2.lastEdited = newEditDate - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(dateFormatter.string(from: newEditDate))\n\n/posts-id/2/\n\(secondTitle)\n\(dateFormatter.string(from: newEditDate))\n\(dateFormatter.string(from: secondPostDate))\n\n\(author.name)\n/authors/\(author.username)/\n\n\(try post2.description())\n\n\n\n/posts-id/1/\n\(post.title)\n\(dateFormatter.string(from: firstPostDate))\n\(dateFormatter.string(from: firstPostDate))\n\n\(author.name)\n/authors/\(author.username)/\n\n\(try post.description())\n\n\n" + let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: newEditDate))\n\nhttps://www.steampress.io/posts-id/2/\n\(secondTitle)\n\(dateFormatter.string(from: newEditDate))\n\(dateFormatter.string(from: secondPostDate))\n\n\(author.name)\nhttps://www.steampress.io/authors/\(author.username)/\n\n\(try post2.description())\n\n\n\nhttps://www.steampress.io/posts-id/1/\n\(post.title)\n\(dateFormatter.string(from: firstPostDate))\n\(dateFormatter.string(from: firstPostDate))\n\n\(author.name)\nhttps://www.steampress.io/authors/\(author.username)/\n\n\(try post.description())\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -189,30 +165,12 @@ class AtomFeedTests: XCTestCase { let post = testData.post let author = testData.author - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\n/posts-id/1/\n\(post.title)\n\(dateFormatter.string(from: post.created))\n\(dateFormatter.string(from: post.created))\n\n\(author.name)\n/authors/\(author.username)/\n\n\(try post.description())\n\n\n\n\n" + let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\nhttps://www.steampress.io/posts-id/1/\n\(post.title)\n\(dateFormatter.string(from: post.created))\n\(dateFormatter.string(from: post.created))\n\n\(author.name)\nhttps://www.steampress.io/authors/\(author.username)/\n\n\(try post.description())\n\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) XCTAssertEqual(actualXmlResponse, expectedXML) } - func testThatFullLinksWorksForPosts() throws { - testWorld = TestWorld.create(path: "blog") - - let testData = try testWorld.createPost() - - let post = testData.post - let author = testData.author - - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://geeks.brokenhands.io/blog/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\nhttps://geeks.brokenhands.io/blog/posts-id/1/\n\(post.title)\n\(dateFormatter.string(from: post.created))\n\(dateFormatter.string(from: post.created))\n\n\(author.name)\nhttps://geeks.brokenhands.io/blog/authors/\(author.username)/\n\n\(try post.description())\n\n\n" - - let fullPath = "/blog/atom.xml" - let actualXmlResponse = try testWorld.getResponseString(to: fullPath, headers: [ - "X-Forwarded-Proto": "https", - "X-Forwarded-For": "geeks.brokenhands.io" - ]) - XCTAssertEqual(actualXmlResponse, expectedXML) - } - func testCorrectHeaderSetForAtomFeed() throws { testWorld = TestWorld.create() let actualXmlResponse = try testWorld.getResponse(to: atomPath) @@ -228,7 +186,7 @@ class AtomFeedTests: XCTestCase { let post = testData.post let author = testData.author - let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\n/\n\n\nSteamPress\n2017-09-20T00:25:08Z\n\n/posts-id/1/\n\(post.title)\n2017-09-20T00:25:08Z\n2017-09-20T00:25:08Z\n\n\(author.name)\n/authors/\(author.username)/\n\n\(try post.description())\n\n\n" + let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n2017-09-20T00:25:08Z\n\nhttps://www.steampress.io/posts-id/1/\n\(post.title)\n2017-09-20T00:25:08Z\n2017-09-20T00:25:08Z\n\n\(author.name)\nhttps://www.steampress.io/authors/\(author.username)/\n\n\(try post.description())\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) XCTAssertEqual(actualXmlResponse, expectedXML) From 3da52da5b4735cba8dc58af5a8b9e426286eaf4f Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Mon, 6 Apr 2020 15:40:34 +0100 Subject: [PATCH 55/70] Migrate the RSS feed tests --- .../Feed Tests/RSSFeedTests.swift | 56 +++++-------------- 1 file changed, 13 insertions(+), 43 deletions(-) diff --git a/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift b/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift index aeca4290..3fd4c4fb 100644 --- a/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift +++ b/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift @@ -27,7 +27,7 @@ class RSSFeedTests: XCTestCase { func testNoPostsReturnsCorrectRSSFeed() throws { testWorld = TestWorld.create() - let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n" + let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: rssPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -37,7 +37,7 @@ class RSSFeedTests: XCTestCase { testWorld = TestWorld.create() let testData = try testWorld.createPost() let post = testData.post - let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\n/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" + let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\nhttps://www.steampress.io/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: rssPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -53,7 +53,7 @@ class RSSFeedTests: XCTestCase { let contents = "This is some short contents" let post2 = try testWorld.createPost(title: anotherTitle, contents: contents, slugUrl: "another-title", author: author).post - let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post2.created))\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n\(anotherTitle)\n\n\n\(contents)\n\n\n/posts/another-title/\n\n\(dateFormatter.string(from: post2.created))\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\n/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" + let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post2.created))\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n\(anotherTitle)\n\n\n\(contents)\n\n\nhttps://www.steampress.io/posts/another-title/\n\n\(dateFormatter.string(from: post2.created))\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\nhttps://www.steampress.io/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: rssPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -66,7 +66,7 @@ class RSSFeedTests: XCTestCase { _ = try testWorld.createPost(title: "A Draft Post", published: false) - let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\n/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" + let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\nhttps://www.steampress.io/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: rssPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -80,7 +80,7 @@ class RSSFeedTests: XCTestCase { let testData = try testWorld.createPost() let post = testData.post - let expectedXML = "\n\n\n\n\(title)\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch \(title)\nSearch\n/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\n/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" + let expectedXML = "\n\n\n\n\(title)\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch \(title)\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\nhttps://www.steampress.io/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: rssPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -94,7 +94,7 @@ class RSSFeedTests: XCTestCase { let testData = try testWorld.createPost() let post = testData.post - let expectedXML = "\n\n\n\nSteamPress Blog\n/\n\(description)\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\n/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" + let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\n\(description)\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\nhttps://www.steampress.io/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: rssPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -103,7 +103,7 @@ class RSSFeedTests: XCTestCase { func testRSSFeedEndpointAddedToCorrectEndpointWhenBlogInSubPath() throws { testWorld = TestWorld.create(path: "blog-path") - let expectedXML = "\n\n\n\nSteamPress Blog\n/blog-path/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\nSearch SteamPress Blog\nSearch\n/blog-path/search?\nterm\n\n\n\n" + let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/blog-path/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/blog-path/search?\nterm\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: blogRSSPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -114,7 +114,7 @@ class RSSFeedTests: XCTestCase { let testData = try testWorld.createPost() let post = testData.post - let expectedXML = "\n\n\n\nSteamPress Blog\n/blog-path/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\n/blog-path/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\n/blog-path/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" + let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/blog-path/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/blog-path/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\nhttps://www.steampress.io/blog-path/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: blogRSSPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -125,7 +125,7 @@ class RSSFeedTests: XCTestCase { let feedInformation = FeedInformation(copyright: copyright) testWorld = TestWorld.create(feedInformation: feedInformation) - let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(copyright)\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n" + let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(copyright)\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: rssPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -136,48 +136,18 @@ class RSSFeedTests: XCTestCase { let testData = try testWorld.createPost(tags: ["Vapor 2", "Engineering"]) let post = testData.post - let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\n/posts/\(post.slugUrl)/\n\nVapor 2\nEngineering\n\(dateFormatter.string(from: post.created))\n\n\n\n" + let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\nhttps://www.steampress.io/posts/\(post.slugUrl)/\n\nVapor 2\nEngineering\n\(dateFormatter.string(from: post.created))\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: rssPath) XCTAssertEqual(actualXmlResponse, expectedXML) } - func testThatLinksComesFromRequestCorrectly() throws { - testWorld = TestWorld.create(path: "blog-path") - let testData = try testWorld.createPost() - let post = testData.post - - let expectedXML = "\n\n\n\nSteamPress Blog\nhttp://geeks.brokenhands.io/blog-path/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\nhttp://geeks.brokenhands.io/blog-path/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\nhttp://geeks.brokenhands.io/blog-path/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" - - let fullPath = "/blog-path/rss.xml" - let actualXmlResponse = try testWorld.getResponseString(to: fullPath, headers: [ - "X-Forwarded-Proto": "http", - "X-Forwarded-For": "geeks.brokenhands.io" - ]) - XCTAssertEqual(actualXmlResponse, expectedXML) - } - - func testThatLinksSpecifyHTTPSWhenComingFromReverseProxy() throws { - testWorld = TestWorld.create(path: "blog-path") - let testData = try testWorld.createPost() - let post = testData.post - - let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://geeks.brokenhands.io/blog-path/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\nhttps://geeks.brokenhands.io/blog-path/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\nhttps://geeks.brokenhands.io/blog-path/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" - - let fullPath = "/blog-path/rss.xml" - let actualXmlResponse = try testWorld.getResponseString(to: fullPath, headers: [ - "X-Forwarded-Proto": "https", - "X-Forwarded-For": "geeks.brokenhands.io" - ]) - XCTAssertEqual(actualXmlResponse, expectedXML) - } - func testImageIsProvidedIfSupplied() throws { let image = "https://static.brokenhands.io/images/brokenhands.png" let feedInformation = FeedInformation(imageURL: image) testWorld = TestWorld.create(feedInformation: feedInformation) - let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\n\(image)\nSteamPress Blog\n/\n\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n" + let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\n\(image)\nSteamPress Blog\nhttps://www.steampress.io/\n\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: rssPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -196,7 +166,7 @@ class RSSFeedTests: XCTestCase { let testData = try testWorld.createPost(createdDate: createDate) let post = testData.post - let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\nWed, 20 Sep 2017 00:25:08 GMT\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\n/posts/\(post.slugUrl)/\n\nWed, 20 Sep 2017 00:25:08 GMT\n\n\n\n" + let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\nWed, 20 Sep 2017 00:25:08 GMT\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\nhttps://www.steampress.io/posts/\(post.slugUrl)/\n\nWed, 20 Sep 2017 00:25:08 GMT\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: rssPath) XCTAssertEqual(actualXmlResponse, expectedXML) @@ -208,7 +178,7 @@ class RSSFeedTests: XCTestCase { let testData = try testWorld.createPost(contents: contents) let post = testData.post - let expectedXML = "\n\n\n\nSteamPress Blog\n/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\n/search?\nterm\n\n\n\n\(post.title)\n\n\nThis is a post that contains some text. Formatting should be removed\n\n\n/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" + let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n\(post.title)\n\n\nThis is a post that contains some text. Formatting should be removed\n\n\nhttps://www.steampress.io/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" let actualXmlResponse = try testWorld.getResponseString(to: rssPath) XCTAssertEqual(actualXmlResponse, expectedXML) From be402b0961fb778f055a988da80a42c4615e7178 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Mon, 6 Apr 2020 16:33:30 +0100 Subject: [PATCH 56/70] Fix code coverage --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb84d1f1..d27b0d04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - name: Setup container for codecov upload run: apt-get update && apt-get install curl - name: Process coverage file - run: llvm-cov show .build/x86_64-unknown-linux/debug/SteamPressPackageTests.xctest -instr-profile=.build/x86_64-unknown-linux/debug/codecov/default.profdata > coverage.txt + run: llvm-cov show .build/x86_64-unknown-linux-gnu/debug/SteamPressPackageTests.xctest -instr-profile=.build/debug/codecov/default.profdata > coverage.txt - name: Upload code coverage uses: codecov/codecov-action@v1 with: From a3abe2dde62068edeed2286725aaf5a7156a6205 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Tue, 14 Apr 2020 14:13:22 +0100 Subject: [PATCH 57/70] Start working out application and config --- .../SteamPress/SteamPress+Application.swift | 56 +++++++++++++++++++ .../SteamPressRoutesLifecycleHandler.swift | 52 +++++++++++++++++ Sources/SteamPress/SteampressLifecyle.swift | 48 ++++++++-------- 3 files changed, 132 insertions(+), 24 deletions(-) create mode 100644 Sources/SteamPress/SteamPress+Application.swift create mode 100644 Sources/SteamPress/SteamPressRoutesLifecycleHandler.swift diff --git a/Sources/SteamPress/SteamPress+Application.swift b/Sources/SteamPress/SteamPress+Application.swift new file mode 100644 index 00000000..846b084f --- /dev/null +++ b/Sources/SteamPress/SteamPress+Application.swift @@ -0,0 +1,56 @@ +import Vapor + +extension Application { + public struct SteamPress { + let application: Application + + final class Storage { + let databases: Databases + let migrations: Migrations + + init(threadPool: NIOThreadPool, on eventLoopGroup: EventLoopGroup) { + self.databases = Databases( + threadPool: threadPool, + on: eventLoopGroup + ) + self.migrations = .init() + } + } + + struct Key: StorageKey { + typealias Value = Storage + } + + var storage: Storage { + if self.application.storage[Key.self] == nil { + self.initialize() + } + return self.application.storage[Key.self]! + } + + func initialize() { + self.application.storage[Key.self] = .init( + threadPool: self.application.threadPool, + on: self.application.eventLoopGroup + ) + self.application.lifecycle.use(SteamPressRoutesLifecycleHandler()) + } + } + + public var steampress: SteamPress { + .init(application: self) + } + + public var configuration: SteamPressConfiguration { + get { + self.steampress.storage.configuration + } + set { + + } + } +} + +public struct SteamPressConfiguration { + +} diff --git a/Sources/SteamPress/SteamPressRoutesLifecycleHandler.swift b/Sources/SteamPress/SteamPressRoutesLifecycleHandler.swift new file mode 100644 index 00000000..3f02104f --- /dev/null +++ b/Sources/SteamPress/SteamPressRoutesLifecycleHandler.swift @@ -0,0 +1,52 @@ +import Vapor + +public struct SteamPressRoutesLifecycleHandler: LifecycleHandler { + + let blogPath: String? + let feedInformation: FeedInformation + let postsPerPage: Int + let enableAuthorPages: Bool + let enableTagPages: Bool + + public init( + blogPath: String?, + feedInformation: FeedInformation, + postsPerPage: Int, + enableAuthorPages: Bool, + enableTagPages: Bool) { + self.blogPath = blogPath + self.feedInformation = feedInformation + self.postsPerPage = postsPerPage + self.enableAuthorPages = enableAuthorPages + self.enableTagPages = enableTagPages + } + + public func willBoot(_ application: Application) throws { + let router = application.routes + let pathCreator = BlogPathCreator(blogPath: self.blogPath) + + let feedController = FeedController(pathCreator: pathCreator, feedInformation: self.feedInformation) + let apiController = APIController() + let blogController = BlogController(pathCreator: pathCreator, enableAuthorPages: self.enableAuthorPages, enableTagPages: self.enableTagPages, postsPerPage: self.postsPerPage) + let blogAdminController = BlogAdminController(pathCreator: pathCreator) + + let blogRoutes: RoutesBuilder + if let blogPath = blogPath { + blogRoutes = router.grouped(PathComponent(stringLiteral: blogPath)) + } else { + blogRoutes = router.grouped("") + } + let steampressSessionsConfig = SessionsConfiguration(cookieName: "steampress-session") { value in + HTTPCookies.Value(string: value.string) + } + let steampressSessions = SessionsMiddleware(session: application.sessions.driver, configuration: steampressSessionsConfig) + let steampressAuthSessions = BlogAuthSessionsMiddleware() + let sessionedRoutes = blogRoutes.grouped(steampressSessions, steampressAuthSessions) + + try sessionedRoutes.register(collection: feedController) + try sessionedRoutes.register(collection: apiController) + try sessionedRoutes.register(collection: blogController) + try sessionedRoutes.register(collection: blogAdminController) + } +} + diff --git a/Sources/SteamPress/SteampressLifecyle.swift b/Sources/SteamPress/SteampressLifecyle.swift index 09eb7d4f..bf963981 100644 --- a/Sources/SteamPress/SteampressLifecyle.swift +++ b/Sources/SteamPress/SteampressLifecyle.swift @@ -41,29 +41,29 @@ public struct SteampressLifecyle: LifecycleHandler { } public func willBoot(_ application: Application) throws { - let router = application.routes - - let feedController = FeedController(pathCreator: self.pathCreator, feedInformation: self.feedInformation) - let apiController = APIController() - let blogController = BlogController(pathCreator: self.pathCreator, enableAuthorPages: self.enableAuthorPages, enableTagPages: self.enableTagPages, postsPerPage: self.postsPerPage) - let blogAdminController = BlogAdminController(pathCreator: self.pathCreator) - - let blogRoutes: RoutesBuilder - if let blogPath = blogPath { - blogRoutes = router.grouped(PathComponent(stringLiteral: blogPath)) - } else { - blogRoutes = router.grouped("") - } - let steampressSessionsConfig = SessionsConfiguration(cookieName: "steampress-session") { value in - HTTPCookies.Value(string: value.string) - } - let steampressSessions = SessionsMiddleware(session: application.sessions.driver, configuration: steampressSessionsConfig) - let steampressAuthSessions = BlogAuthSessionsMiddleware() - let sessionedRoutes = blogRoutes.grouped(steampressSessions, steampressAuthSessions) - - try sessionedRoutes.register(collection: feedController) - try sessionedRoutes.register(collection: apiController) - try sessionedRoutes.register(collection: blogController) - try sessionedRoutes.register(collection: blogAdminController) +// let router = application.routes +// +// let feedController = FeedController(pathCreator: self.pathCreator, feedInformation: self.feedInformation) +// let apiController = APIController() +// let blogController = BlogController(pathCreator: self.pathCreator, enableAuthorPages: self.enableAuthorPages, enableTagPages: self.enableTagPages, postsPerPage: self.postsPerPage) +// let blogAdminController = BlogAdminController(pathCreator: self.pathCreator) +// +// let blogRoutes: RoutesBuilder +// if let blogPath = blogPath { +// blogRoutes = router.grouped(PathComponent(stringLiteral: blogPath)) +// } else { +// blogRoutes = router.grouped("") +// } +// let steampressSessionsConfig = SessionsConfiguration(cookieName: "steampress-session") { value in +// HTTPCookies.Value(string: value.string) +// } +// let steampressSessions = SessionsMiddleware(session: application.sessions.driver, configuration: steampressSessionsConfig) +// let steampressAuthSessions = BlogAuthSessionsMiddleware() +// let sessionedRoutes = blogRoutes.grouped(steampressSessions, steampressAuthSessions) +// +// try sessionedRoutes.register(collection: feedController) +// try sessionedRoutes.register(collection: apiController) +// try sessionedRoutes.register(collection: blogController) +// try sessionedRoutes.register(collection: blogAdminController) } } From 3905542a21c3ea07cb900c05bc085a5dd0454c8d Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Tue, 14 Apr 2020 14:31:19 +0100 Subject: [PATCH 58/70] Migrate source code to Vapor 4 release and use async password functions --- .../Controllers/Admin/LoginController.swift | 31 ++-- .../Admin/UserAdminController.swift | 45 +++-- .../Extensions/BCrypt+PasswordHasher.swift | 164 ------------------ .../Presenters/BlogAdminPresenter.swift | 2 +- .../SteamPress/Presenters/BlogPresenter.swift | 2 +- .../SteamPress/SteamPress+Application.swift | 32 ++-- 6 files changed, 60 insertions(+), 216 deletions(-) delete mode 100644 Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift diff --git a/Sources/SteamPress/Controllers/Admin/LoginController.swift b/Sources/SteamPress/Controllers/Admin/LoginController.swift index bcbd2751..ae1ceb74 100644 --- a/Sources/SteamPress/Controllers/Admin/LoginController.swift +++ b/Sources/SteamPress/Controllers/Admin/LoginController.swift @@ -59,17 +59,26 @@ struct LoginController: RouteCollection { } return req.blogUserRepository.getUser(username: username).flatMap { user -> EventLoopFuture in - do { - guard let user = user, try req.passwordVerifier.verify(password, created: user.password) else { - let loginError = ["Your username or password is incorrect"] + guard let user = user else { + let loginError = ["Your username or password is incorrect"] + do { return try req.blogPresenter.loginView(loginWarning: false, errors: loginError, username: loginData.username, usernameError: false, passwordError: false, rememberMe: loginData.rememberMe ?? false, pageInformation: req.pageInformation()).encodeResponse(for: req) + } catch { + return req.eventLoop.makeFailedFuture(error) + } + } + return req.password.async.verify(password, created: user.password).flatMap { userAuthenticated in + guard userAuthenticated else { + let loginError = ["Your username or password is incorrect"] + do { + return try req.blogPresenter.loginView(loginWarning: false, errors: loginError, username: loginData.username, usernameError: false, passwordError: false, rememberMe: loginData.rememberMe ?? false, pageInformation: req.pageInformation()).encodeResponse(for: req) + } catch { + return req.eventLoop.makeFailedFuture(error) + } } user.authenticateSession(on: req) return req.eventLoop.future(req.redirect(to: self.pathCreator.createPath(for: "admin"))) } - catch { - return req.eventLoop.makeFailedFuture(error) - } } } @@ -122,9 +131,11 @@ struct LoginController: RouteCollection { } let user = try req.auth.require(BlogUser.self) - user.password = try req.passwordHasher.hash(password) - user.resetPasswordRequired = false - let redirect = req.redirect(to: pathCreator.createPath(for: "admin")) - return req.blogUserRepository.save(user).transform(to: redirect) + return req.password.async.hash(password).flatMap { hashedPassword in + user.password = hashedPassword + user.resetPasswordRequired = false + let redirect = req.redirect(to: self.pathCreator.createPath(for: "admin")) + return req.blogUserRepository.save(user).transform(to: redirect) + } } } diff --git a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift index 4b6e9137..208c1b1e 100644 --- a/Sources/SteamPress/Controllers/Admin/UserAdminController.swift +++ b/Sources/SteamPress/Controllers/Admin/UserAdminController.swift @@ -41,24 +41,19 @@ struct UserAdminController: RouteCollection { return req.eventLoop.makeFailedFuture(Abort(.internalServerError)) } - let hashedPassword: String - do { - hashedPassword = try req.passwordHasher.hash(password) - } catch { - return req.eventLoop.makeFailedFuture(error) - } - let profilePicture = data.profilePicture.isEmptyOrWhitespace() ? nil : data.profilePicture - let twitterHandle = data.twitterHandle.isEmptyOrWhitespace() ? nil : data.twitterHandle - let biography = data.biography.isEmptyOrWhitespace() ? nil : data.biography - let tagline = data.tagline.isEmptyOrWhitespace() ? nil : data.tagline - let newUser = BlogUser(name: name, username: username.lowercased(), password: hashedPassword, profilePicture: profilePicture, twitterHandle: twitterHandle, biography: biography, tagline: tagline) - if let resetPasswordRequired = data.resetPasswordOnLogin, resetPasswordRequired { - newUser.resetPasswordRequired = true - } - return req.blogUserRepository.save(newUser).map { _ in - return req.redirect(to: self.pathCreator.createPath(for: "admin")) + return req.password.async.hash(password).flatMap { hashedPassword in + let profilePicture = data.profilePicture.isEmptyOrWhitespace() ? nil : data.profilePicture + let twitterHandle = data.twitterHandle.isEmptyOrWhitespace() ? nil : data.twitterHandle + let biography = data.biography.isEmptyOrWhitespace() ? nil : data.biography + let tagline = data.tagline.isEmptyOrWhitespace() ? nil : data.tagline + let newUser = BlogUser(name: name, username: username.lowercased(), password: hashedPassword, profilePicture: profilePicture, twitterHandle: twitterHandle, biography: biography, tagline: tagline) + if let resetPasswordRequired = data.resetPasswordOnLogin, resetPasswordRequired { + newUser.resetPasswordRequired = true + } + return req.blogUserRepository.save(newUser).map { _ in + return req.redirect(to: self.pathCreator.createPath(for: "admin")) + } } - } } @@ -108,16 +103,18 @@ struct UserAdminController: RouteCollection { user.resetPasswordRequired = true } + let updatePassword: EventLoopFuture if let password = data.password, password != "" { - do { - user.password = try req.passwordHasher.hash(password) - } catch { - return req.eventLoop.makeFailedFuture(error) + updatePassword = req.password.async.hash(password).map { hashedPassword in + user.password = hashedPassword } + } else { + updatePassword = req.eventLoop.future() + } + return updatePassword.flatMap { + let redirect = req.redirect(to: self.pathCreator.createPath(for: "admin")) + return req.blogUserRepository.save(user).transform(to: redirect) } - - let redirect = req.redirect(to: self.pathCreator.createPath(for: "admin")) - return req.blogUserRepository.save(user).transform(to: redirect) } } catch { return req.eventLoop.makeFailedFuture(error) diff --git a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift b/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift deleted file mode 100644 index e501095f..00000000 --- a/Sources/SteamPress/Extensions/BCrypt+PasswordHasher.swift +++ /dev/null @@ -1,164 +0,0 @@ -import Vapor -import Crypto - -public protocol PasswordHasher { - func `for`(_ request: Request) -> PasswordHasher - func hash(_ plaintext: String) throws -> String -} - -extension BCryptDigest: PasswordHasher { - public func hash(_ plaintext: String) throws -> String { - return try self.hash(plaintext, cost: 12) - } - - public func `for`(_ request: Request) -> PasswordHasher { - return BCryptDigest() - } -} - -public protocol SteamPressPasswordVerifier { - func `for`(_ request: Request) -> SteamPressPasswordVerifier - func verify(_ plaintext: String, created hash: String) throws -> Bool -} - -extension BCryptDigest: SteamPressPasswordVerifier { - public func `for`(_ request: Request) -> SteamPressPasswordVerifier { - return BCryptDigest() - } -} - -public extension Request { - var passwordHasher: PasswordHasher { - self.application.passwordHashers.passwordHasher.for(self) - } - - var passwordVerifier: SteamPressPasswordVerifier { - self.application.passwordVerifiers.passwordVerifier.for(self) - } -} - -public extension Application { - struct PasswordVerifiers { - public struct Provider { - public static var bcrypt: Self { - .init { - $0.passwordVerifiers.use { $0.passwordVerifiers.bcrypt } - } - } - - let run: (Application) -> () - - public init(_ run: @escaping (Application) -> ()) { - self.run = run - } - } - - final class Storage { - var makeVerifier: ((Application) -> SteamPressPasswordVerifier)? - init() { } - } - - struct Key: StorageKey { - typealias Value = Storage - } - - let application: Application - - var bcrypt: BCryptDigest { - return .init() - } - - var passwordVerifier: SteamPressPasswordVerifier { - guard let makeVerifier = self.storage.makeVerifier else { - fatalError("No password verifier configured. Configure with app.passwordVerifiers.use(...)") - } - return makeVerifier(self.application) - } - - public func use(_ provider: Provider) { - provider.run(self.application) - } - - public func use(_ makeVerifier: @escaping (Application) -> (SteamPressPasswordVerifier)) { - self.storage.makeVerifier = makeVerifier - } - - func initialize() { - self.application.storage[Key.self] = .init() - self.use(.bcrypt) - } - - private var storage: Storage { - if self.application.storage[Key.self] == nil { - self.initialize() - } - return self.application.storage[Key.self]! - } - } - - var passwordVerifiers: PasswordVerifiers { - .init(application: self) - } - - struct PasswordHashers { - public struct Provider { - public static var bcrypt: Self { - .init { - $0.passwordHashers.use { $0.passwordHashers.bcrypt } - } - } - - let run: (Application) -> () - - public init(_ run: @escaping (Application) -> ()) { - self.run = run - } - } - - final class Storage { - var makeHasher: ((Application) -> PasswordHasher)? - init() { } - } - - struct Key: StorageKey { - typealias Value = Storage - } - - let application: Application - - var bcrypt: BCryptDigest { - return .init() - } - - var passwordHasher: PasswordHasher { - guard let makeHasher = self.storage.makeHasher else { - fatalError("No password hasher configured. Configure with app.passwordHashers.use(...)") - } - return makeHasher(self.application) - } - - public func use(_ provider: Provider) { - provider.run(self.application) - } - - public func use(_ makeHasher: @escaping (Application) -> (PasswordHasher)) { - self.storage.makeHasher = makeHasher - } - - func initialize() { - self.application.storage[Key.self] = .init() - self.use(.bcrypt) - } - - private var storage: Storage { - if self.application.storage[Key.self] == nil { - self.initialize() - } - return self.application.storage[Key.self]! - } - } - - var passwordHashers: PasswordHashers { - .init(application: self) - } -} diff --git a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift index b603f03d..eaa5c13d 100644 --- a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift @@ -51,7 +51,7 @@ extension Application { let application: Application var view: ViewBlogAdminPresenter { - return .init(pathCreator: self.storage.pathCreator, viewRenderer: self.application.views.renderer, eventLoopGroup: self.application.eventLoopGroup, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) + return .init(pathCreator: self.storage.pathCreator, viewRenderer: self.application.view, eventLoopGroup: self.application.eventLoopGroup, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) } var adminPresenter: BlogAdminPresenter { diff --git a/Sources/SteamPress/Presenters/BlogPresenter.swift b/Sources/SteamPress/Presenters/BlogPresenter.swift index 91698857..7270a960 100644 --- a/Sources/SteamPress/Presenters/BlogPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogPresenter.swift @@ -52,7 +52,7 @@ extension Application { let application: Application var view: ViewBlogPresenter { - return .init(viewRenderer: self.application.views.renderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: self.application.eventLoopGroup) + return .init(viewRenderer: self.application.view, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: self.application.eventLoopGroup) } var blogPresenter: BlogPresenter { diff --git a/Sources/SteamPress/SteamPress+Application.swift b/Sources/SteamPress/SteamPress+Application.swift index 846b084f..8bc4fd3b 100644 --- a/Sources/SteamPress/SteamPress+Application.swift +++ b/Sources/SteamPress/SteamPress+Application.swift @@ -5,15 +5,15 @@ extension Application { let application: Application final class Storage { - let databases: Databases - let migrations: Migrations +// let databases: Databases +// let migrations: Migrations init(threadPool: NIOThreadPool, on eventLoopGroup: EventLoopGroup) { - self.databases = Databases( - threadPool: threadPool, - on: eventLoopGroup - ) - self.migrations = .init() +// self.databases = Databases( +// threadPool: threadPool, +// on: eventLoopGroup +// ) +// self.migrations = .init() } } @@ -33,7 +33,7 @@ extension Application { threadPool: self.application.threadPool, on: self.application.eventLoopGroup ) - self.application.lifecycle.use(SteamPressRoutesLifecycleHandler()) +// self.application.lifecycle.use(SteamPressRoutesLifecycleHandler()) } } @@ -41,14 +41,14 @@ extension Application { .init(application: self) } - public var configuration: SteamPressConfiguration { - get { - self.steampress.storage.configuration - } - set { - - } - } +// public var configuration: SteamPressConfiguration { +// get { +// self.steampress.storage.configuration +// } +// set { +// +// } +// } } public struct SteamPressConfiguration { From e411cfd585c777e5304c113dbb6296f97d46f4c4 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Tue, 14 Apr 2020 14:36:32 +0100 Subject: [PATCH 59/70] Make the tests compile with Vapor 4 release --- .../Fakes/PlaintextHasher.swift | 51 ------------------- .../Fakes/ReversedPasswordHasher.swift | 43 ++++------------ .../Helpers/TestWorld+Application.swift | 9 ++-- 3 files changed, 12 insertions(+), 91 deletions(-) delete mode 100644 Tests/SteamPressTests/Fakes/PlaintextHasher.swift diff --git a/Tests/SteamPressTests/Fakes/PlaintextHasher.swift b/Tests/SteamPressTests/Fakes/PlaintextHasher.swift deleted file mode 100644 index 09d06cac..00000000 --- a/Tests/SteamPressTests/Fakes/PlaintextHasher.swift +++ /dev/null @@ -1,51 +0,0 @@ -import Vapor -import SteamPress - -struct PlaintextHasher: PasswordHasher { - - func hash(_ plaintext: String) throws -> String { - return plaintext - } - - func `for`(_ request: Request) -> PasswordHasher { - return PlaintextHasher() - } -} - -extension Application.PasswordHashers.Provider { - public static var plaintext: Self { - .init { - $0.passwordHashers.use { $0.passwordHashers.plaintext } - } - } -} - -extension Application.PasswordHashers { - var plaintext: PlaintextHasher { - return .init() - } -} - -extension PlaintextVerifier: SteamPressPasswordVerifier { - public func `for`(_ request: Request) -> SteamPressPasswordVerifier { - return PlaintextVerifier() - } - - public func verify(_ plaintext: String, created hash: String) throws -> Bool { - return plaintext == hash - } -} - -extension Application.PasswordVerifiers { - var plaintext: PlaintextVerifier { - return .init() - } -} - -extension Application.PasswordVerifiers.Provider { - public static var plaintext: Self { - .init { - $0.passwordVerifiers.use { $0.passwordVerifiers.plaintext } - } - } -} diff --git a/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift b/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift index 587dafa2..14d360b9 100644 --- a/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift +++ b/Tests/SteamPressTests/Fakes/ReversedPasswordHasher.swift @@ -1,48 +1,23 @@ import Vapor import SteamPress -struct ReversedPasswordHasher: PasswordHasher, SteamPressPasswordVerifier { - func `for`(_ request: Request) -> PasswordHasher { - return ReversedPasswordHasher() - } - - func `for`(_ request: Request) -> SteamPressPasswordVerifier { - return ReversedPasswordHasher() - } +struct ReversedPasswordHasher: PasswordHasher { - func hash(_ plaintext: String) throws -> String { - return String(plaintext.reversed()) + func verify(_ password: Password, created digest: Digest) throws -> Bool where Password : DataProtocol, Digest : DataProtocol { + return password.reversed() == Array(digest) } - func verify(_ plaintext: String, created hash: String) throws -> Bool { - return plaintext == String(hash.reversed()) - } -} - -extension Application.PasswordHashers.Provider { - public static var reversed: Self { - .init { - $0.passwordHashers.use { $0.passwordHashers.reversed } - } - } -} - -extension Application.PasswordHashers { - var reversed: ReversedPasswordHasher { - return .init() - } -} - -extension Application.PasswordVerifiers { - var reversed: ReversedPasswordHasher { - return .init() + func hash(_ password: Password) throws -> [UInt8] where Password : DataProtocol { + return password.reversed() } } -extension Application.PasswordVerifiers.Provider { +extension Application.Passwords.Provider { public static var reversed: Self { .init { - $0.passwordVerifiers.use { $0.passwordVerifiers.reversed } + $0.passwords.use { _ in + ReversedPasswordHasher() + } } } } diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift index 3fa0ab09..7647c63f 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift @@ -44,14 +44,11 @@ extension TestWorld { switch passwordHasherToUse { case .real: - application.passwordHashers.use(.bcrypt) - application.passwordVerifiers.use(.bcrypt) + application.passwords.use(.bcrypt) case .plaintext: - application.passwordHashers.use(.plaintext) - application.passwordVerifiers.use(.plaintext) + application.passwords.use(.plaintext) case .reversed: - application.passwordVerifiers.use(.reversed) - application.passwordHashers.use(.reversed) + application.passwords.use(.reversed) } return application From 830855e83f488b433e804d55196324a561bebb89 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Tue, 14 Apr 2020 14:42:12 +0100 Subject: [PATCH 60/70] Tmp fix to get the tests working again --- Sources/SteamPress/SteampressLifecyle.swift | 48 ++++++++++----------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Sources/SteamPress/SteampressLifecyle.swift b/Sources/SteamPress/SteampressLifecyle.swift index bf963981..09eb7d4f 100644 --- a/Sources/SteamPress/SteampressLifecyle.swift +++ b/Sources/SteamPress/SteampressLifecyle.swift @@ -41,29 +41,29 @@ public struct SteampressLifecyle: LifecycleHandler { } public func willBoot(_ application: Application) throws { -// let router = application.routes -// -// let feedController = FeedController(pathCreator: self.pathCreator, feedInformation: self.feedInformation) -// let apiController = APIController() -// let blogController = BlogController(pathCreator: self.pathCreator, enableAuthorPages: self.enableAuthorPages, enableTagPages: self.enableTagPages, postsPerPage: self.postsPerPage) -// let blogAdminController = BlogAdminController(pathCreator: self.pathCreator) -// -// let blogRoutes: RoutesBuilder -// if let blogPath = blogPath { -// blogRoutes = router.grouped(PathComponent(stringLiteral: blogPath)) -// } else { -// blogRoutes = router.grouped("") -// } -// let steampressSessionsConfig = SessionsConfiguration(cookieName: "steampress-session") { value in -// HTTPCookies.Value(string: value.string) -// } -// let steampressSessions = SessionsMiddleware(session: application.sessions.driver, configuration: steampressSessionsConfig) -// let steampressAuthSessions = BlogAuthSessionsMiddleware() -// let sessionedRoutes = blogRoutes.grouped(steampressSessions, steampressAuthSessions) -// -// try sessionedRoutes.register(collection: feedController) -// try sessionedRoutes.register(collection: apiController) -// try sessionedRoutes.register(collection: blogController) -// try sessionedRoutes.register(collection: blogAdminController) + let router = application.routes + + let feedController = FeedController(pathCreator: self.pathCreator, feedInformation: self.feedInformation) + let apiController = APIController() + let blogController = BlogController(pathCreator: self.pathCreator, enableAuthorPages: self.enableAuthorPages, enableTagPages: self.enableTagPages, postsPerPage: self.postsPerPage) + let blogAdminController = BlogAdminController(pathCreator: self.pathCreator) + + let blogRoutes: RoutesBuilder + if let blogPath = blogPath { + blogRoutes = router.grouped(PathComponent(stringLiteral: blogPath)) + } else { + blogRoutes = router.grouped("") + } + let steampressSessionsConfig = SessionsConfiguration(cookieName: "steampress-session") { value in + HTTPCookies.Value(string: value.string) + } + let steampressSessions = SessionsMiddleware(session: application.sessions.driver, configuration: steampressSessionsConfig) + let steampressAuthSessions = BlogAuthSessionsMiddleware() + let sessionedRoutes = blogRoutes.grouped(steampressSessions, steampressAuthSessions) + + try sessionedRoutes.register(collection: feedController) + try sessionedRoutes.register(collection: apiController) + try sessionedRoutes.register(collection: blogController) + try sessionedRoutes.register(collection: blogAdminController) } } From af3d3f4811bbf7470d0636256fbd86ba38d898c4 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Tue, 14 Apr 2020 15:21:22 +0100 Subject: [PATCH 61/70] Get SteamPress working with weird extension stuff --- .../Presenters/BlogAdminPresenter.swift | 7 +- .../SteamPress/SteamPress+Application.swift | 72 +++++++++++-------- .../SteamPressRoutesLifecycleHandler.swift | 29 +++----- Sources/SteamPress/SteampressLifecyle.swift | 69 ------------------ .../Helpers/TestWorld+Application.swift | 21 +++--- Tests/SteamPressTests/ProviderTests.swift | 5 +- 6 files changed, 68 insertions(+), 135 deletions(-) delete mode 100644 Sources/SteamPress/SteampressLifecyle.swift diff --git a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift index eaa5c13d..5c288f55 100644 --- a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift @@ -75,10 +75,11 @@ extension Application { } var storage: Storage { - guard let storage = self.application.storage[Key.self] else { - fatalError("BlogAdminPresenters not configured. Configure with app.adminPresenters.initialize()") + if self.application.storage[Key.self] == nil { + let pathCreator = BlogPathCreator(blogPath: self.application.steampress.configuration.blogPath) + initialize(pathCreator: pathCreator) } - return storage + return self.application.storage[Key.self]! } } diff --git a/Sources/SteamPress/SteamPress+Application.swift b/Sources/SteamPress/SteamPress+Application.swift index 8bc4fd3b..2cae2871 100644 --- a/Sources/SteamPress/SteamPress+Application.swift +++ b/Sources/SteamPress/SteamPress+Application.swift @@ -1,22 +1,23 @@ import Vapor extension Application { - public struct SteamPress { + public class SteamPress { let application: Application + let lifecycleHandler: SteamPressRoutesLifecycleHandler + + init(application: Application, lifecycleHandler: SteamPressRoutesLifecycleHandler) { + self.application = application + self.lifecycleHandler = lifecycleHandler + } final class Storage { -// let databases: Databases -// let migrations: Migrations - - init(threadPool: NIOThreadPool, on eventLoopGroup: EventLoopGroup) { -// self.databases = Databases( -// threadPool: threadPool, -// on: eventLoopGroup -// ) -// self.migrations = .init() + var configuration: SteamPressConfiguration + + init() { + configuration = SteamPressConfiguration() } } - + struct Key: StorageKey { typealias Value = Storage } @@ -27,30 +28,45 @@ extension Application { } return self.application.storage[Key.self]! } - + func initialize() { - self.application.storage[Key.self] = .init( - threadPool: self.application.threadPool, - on: self.application.eventLoopGroup - ) -// self.application.lifecycle.use(SteamPressRoutesLifecycleHandler()) + self.application.storage[Key.self] = .init() + self.application.lifecycle.use(lifecycleHandler) + } + + public var configuration: SteamPressConfiguration { + get { + self.storage.configuration + } + set { + self.storage.configuration = newValue + self.lifecycleHandler.configuration = newValue + } } } public var steampress: SteamPress { - .init(application: self) + .init(application: self, lifecycleHandler: SteamPressRoutesLifecycleHandler()) } - -// public var configuration: SteamPressConfiguration { -// get { -// self.steampress.storage.configuration -// } -// set { -// -// } -// } } -public struct SteamPressConfiguration { +public class SteamPressConfiguration { + let blogPath: String? + let feedInformation: FeedInformation + let postsPerPage: Int + let enableAuthorPages: Bool + let enableTagPages: Bool + public init( + blogPath: String? = nil, + feedInformation: FeedInformation = FeedInformation(), + postsPerPage: Int = 10, + enableAuthorPages: Bool = true, + enableTagPages: Bool = true) { + self.blogPath = blogPath + self.feedInformation = feedInformation + self.postsPerPage = postsPerPage + self.enableAuthorPages = enableAuthorPages + self.enableTagPages = enableTagPages + } } diff --git a/Sources/SteamPress/SteamPressRoutesLifecycleHandler.swift b/Sources/SteamPress/SteamPressRoutesLifecycleHandler.swift index 3f02104f..7f664f9d 100644 --- a/Sources/SteamPress/SteamPressRoutesLifecycleHandler.swift +++ b/Sources/SteamPress/SteamPressRoutesLifecycleHandler.swift @@ -1,37 +1,24 @@ import Vapor -public struct SteamPressRoutesLifecycleHandler: LifecycleHandler { +public class SteamPressRoutesLifecycleHandler: LifecycleHandler { - let blogPath: String? - let feedInformation: FeedInformation - let postsPerPage: Int - let enableAuthorPages: Bool - let enableTagPages: Bool + var configuration: SteamPressConfiguration - public init( - blogPath: String?, - feedInformation: FeedInformation, - postsPerPage: Int, - enableAuthorPages: Bool, - enableTagPages: Bool) { - self.blogPath = blogPath - self.feedInformation = feedInformation - self.postsPerPage = postsPerPage - self.enableAuthorPages = enableAuthorPages - self.enableTagPages = enableTagPages + public init(configuration: SteamPressConfiguration = SteamPressConfiguration()) { + self.configuration = configuration } public func willBoot(_ application: Application) throws { let router = application.routes - let pathCreator = BlogPathCreator(blogPath: self.blogPath) + let pathCreator = BlogPathCreator(blogPath: self.configuration.blogPath) - let feedController = FeedController(pathCreator: pathCreator, feedInformation: self.feedInformation) + let feedController = FeedController(pathCreator: pathCreator, feedInformation: self.configuration.feedInformation) let apiController = APIController() - let blogController = BlogController(pathCreator: pathCreator, enableAuthorPages: self.enableAuthorPages, enableTagPages: self.enableTagPages, postsPerPage: self.postsPerPage) + let blogController = BlogController(pathCreator: pathCreator, enableAuthorPages: self.configuration.enableAuthorPages, enableTagPages: self.configuration.enableTagPages, postsPerPage: self.configuration.postsPerPage) let blogAdminController = BlogAdminController(pathCreator: pathCreator) let blogRoutes: RoutesBuilder - if let blogPath = blogPath { + if let blogPath = self.configuration.blogPath { blogRoutes = router.grouped(PathComponent(stringLiteral: blogPath)) } else { blogRoutes = router.grouped("") diff --git a/Sources/SteamPress/SteampressLifecyle.swift b/Sources/SteamPress/SteampressLifecyle.swift deleted file mode 100644 index 09eb7d4f..00000000 --- a/Sources/SteamPress/SteampressLifecyle.swift +++ /dev/null @@ -1,69 +0,0 @@ -import Vapor - -public struct SteampressLifecyle: LifecycleHandler { - - let blogPath: String? - let feedInformation: FeedInformation - let postsPerPage: Int - let enableAuthorPages: Bool - let enableTagPages: Bool - let pathCreator: BlogPathCreator - - /** - Initialiser for SteamPress' Provider to add a blog to your Vapor App. You can pass it an optional - `blogPath` to add the blog to. For instance, if you pass in "blog", your blog will be accessible - at http://mysite.com/blog/, or if you pass in `nil` your blog will be added to the root of your - site (i.e. http://mysite.com/) - - Parameter blogPath: The path to add the blog to (see above). - - Parameter feedInformation: Information to vend to the RSS and Atom feeds. Defaults to empty information. - - Parameter postsPerPage: The number of posts to show per page on the main index page of the blog. Defaults to 10. - - Parameter enableAuthorsPages: Flag used to determine whether to publicly expose the authors endpoints - or not. Defaults to true. - - Parameter enableTagsPages: Flag used to determine whether to publicy expose the tags endpoints or not. - Defaults to true. - */ - public init( - blogPath: String? = nil, - feedInformation: FeedInformation = FeedInformation(), - postsPerPage: Int = 10, - enableAuthorPages: Bool = true, - enableTagPages: Bool = true) { - self.blogPath = blogPath - self.feedInformation = feedInformation - self.postsPerPage = postsPerPage - self.enableAuthorPages = enableAuthorPages - self.enableTagPages = enableTagPages - self.pathCreator = BlogPathCreator(blogPath: self.blogPath) - } - - public func tmpSetup(_ application: Application) { - application.adminPresenters.initialize(pathCreator: pathCreator) - } - - public func willBoot(_ application: Application) throws { - let router = application.routes - - let feedController = FeedController(pathCreator: self.pathCreator, feedInformation: self.feedInformation) - let apiController = APIController() - let blogController = BlogController(pathCreator: self.pathCreator, enableAuthorPages: self.enableAuthorPages, enableTagPages: self.enableTagPages, postsPerPage: self.postsPerPage) - let blogAdminController = BlogAdminController(pathCreator: self.pathCreator) - - let blogRoutes: RoutesBuilder - if let blogPath = blogPath { - blogRoutes = router.grouped(PathComponent(stringLiteral: blogPath)) - } else { - blogRoutes = router.grouped("") - } - let steampressSessionsConfig = SessionsConfiguration(cookieName: "steampress-session") { value in - HTTPCookies.Value(string: value.string) - } - let steampressSessions = SessionsMiddleware(session: application.sessions.driver, configuration: steampressSessionsConfig) - let steampressAuthSessions = BlogAuthSessionsMiddleware() - let sessionedRoutes = blogRoutes.grouped(steampressSessions, steampressAuthSessions) - - try sessionedRoutes.register(collection: feedController) - try sessionedRoutes.register(collection: apiController) - try sessionedRoutes.register(collection: blogController) - try sessionedRoutes.register(collection: blogAdminController) - } -} diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift index 7647c63f..cde7658e 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift @@ -16,16 +16,17 @@ extension TestWorld { let application = Application(.testing, .shared(eventLoopGroup)) - let steampress = SteamPress.SteampressLifecyle( - blogPath: path, - feedInformation: feedInformation, - postsPerPage: postsPerPage, - enableAuthorPages: enableAuthorPages, - enableTagPages: enableTagPages) - application.lifecycle.use(steampress) - - #warning("This should be removed") - steampress.tmpSetup(application) +// let steampress = SteamPress.SteampressLifecyle( +// blogPath: path, +// feedInformation: feedInformation, +// postsPerPage: postsPerPage, +// enableAuthorPages: enableAuthorPages, +// enableTagPages: enableTagPages) + application.steampress.configuration = SteamPressConfiguration(blogPath: path, feedInformation: feedInformation, postsPerPage: postsPerPage, enableAuthorPages: enableAuthorPages, enableTagPages: enableTagPages) +// application.lifecycle.use(steampress) +// +// #warning("This should be removed") +// steampress.tmpSetup(application) application.blogRepositories.use { _ in return repository } diff --git a/Tests/SteamPressTests/ProviderTests.swift b/Tests/SteamPressTests/ProviderTests.swift index 33933c4c..61b8d2df 100644 --- a/Tests/SteamPressTests/ProviderTests.swift +++ b/Tests/SteamPressTests/ProviderTests.swift @@ -3,11 +3,8 @@ import XCTest import Vapor class ProviderTests: XCTestCase { - func testUsingProviderSetsCorrectServices() throws { + func testSteamPressSetsCorrectServices() throws { let app = Application() - let lifecycle = SteamPress.SteampressLifecyle() - lifecycle.tmpSetup(app) - app.lifecycle.use(lifecycle) app.middleware.use(BlogRememberMeMiddleware()) app.middleware.use(SessionsMiddleware(session: app.sessions.driver)) From 6038e9be4dc24860eceae6f78e2b727b35547ffe Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Tue, 14 Apr 2020 15:33:21 +0100 Subject: [PATCH 62/70] Migrate to throwing setup and teardown functions --- .../APITests/APITagControllerTests.swift | 4 +-- .../AdminTests/AccessControlTests.swift | 8 ++--- .../AdminTests/AdminPageTests.swift | 4 +-- .../AdminTests/AdminPostTests.swift | 10 +++--- .../AdminTests/AdminUserTests.swift | 14 ++++---- .../AdminTests/LoginTests.swift | 10 +++--- .../BlogTests/AuthorTests.swift | 14 ++++---- .../BlogTests/DisabledBlogTagTests.swift | 4 +-- .../BlogTests/IndexTests.swift | 14 ++++---- .../SteamPressTests/BlogTests/PostTests.swift | 6 ++-- .../BlogTests/SearchTests.swift | 6 ++-- .../SteamPressTests/BlogTests/TagTests.swift | 16 +++++----- .../Feed Tests/AtomFeedTests.swift | 30 ++++++++--------- .../Feed Tests/RSSFeedTests.swift | 32 +++++++++---------- Tests/SteamPressTests/Helpers/TestWorld.swift | 4 +-- .../ViewTests/BlogViewTests.swift | 4 +-- 16 files changed, 90 insertions(+), 90 deletions(-) diff --git a/Tests/SteamPressTests/APITests/APITagControllerTests.swift b/Tests/SteamPressTests/APITests/APITagControllerTests.swift index 3e28db15..253c31f3 100644 --- a/Tests/SteamPressTests/APITests/APITagControllerTests.swift +++ b/Tests/SteamPressTests/APITests/APITagControllerTests.swift @@ -9,8 +9,8 @@ class APITagControllerTests: XCTestCase { // MARK: - Overrides - override func setUp() { - testWorld = TestWorld.create() + override func setUpWithError() throws { + testWorld = try TestWorld.create() } override func tearDownWithError() throws { diff --git a/Tests/SteamPressTests/AdminTests/AccessControlTests.swift b/Tests/SteamPressTests/AdminTests/AccessControlTests.swift index 3ff12269..fb547657 100644 --- a/Tests/SteamPressTests/AdminTests/AccessControlTests.swift +++ b/Tests/SteamPressTests/AdminTests/AccessControlTests.swift @@ -11,13 +11,13 @@ class AccessControlTests: XCTestCase { // MARK: - Overrides - override func setUp() { - testWorld = TestWorld.create(path: "blog") + override func setUpWithError() throws { + testWorld = try TestWorld.create(path: "blog") user = testWorld.createUser() } - override func tearDown() { - XCTAssertNoThrow(try testWorld.shutdown()) + override func tearDownWithError() throws { + try testWorld.shutdown() } // MARK: - Tests diff --git a/Tests/SteamPressTests/AdminTests/AdminPageTests.swift b/Tests/SteamPressTests/AdminTests/AdminPageTests.swift index 28d9086c..050cfcb5 100644 --- a/Tests/SteamPressTests/AdminTests/AdminPageTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminPageTests.swift @@ -5,7 +5,7 @@ import SteamPress class AdminPageTests: XCTestCase { func testAdminPagePassesCorrectInformationToPresenter() throws { - let testWorld = TestWorld.create(websiteURL: "/") + let testWorld = try TestWorld.create(websiteURL: "/") let user = testWorld.createUser(username: "leia") let testData1 = try testWorld.createPost(author: user) let testData2 = try testWorld.createPost(title: "A second post", author: user) @@ -24,6 +24,6 @@ class AdminPageTests: XCTestCase { XCTAssertEqual(presenter.adminViewPageInformation?.websiteURL.absoluteString, "/") XCTAssertEqual(presenter.adminViewPageInformation?.currentPageURL.absoluteString, "/admin/") - XCTAssertNoThrow(try testWorld.shutdown()) + try testWorld.shutdown() } } diff --git a/Tests/SteamPressTests/AdminTests/AdminPostTests.swift b/Tests/SteamPressTests/AdminTests/AdminPostTests.swift index dd87d9b9..bbbb0424 100644 --- a/Tests/SteamPressTests/AdminTests/AdminPostTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminPostTests.swift @@ -15,13 +15,13 @@ class AdminPostTests: XCTestCase { // MARK: - Overrides - override func setUp() { - testWorld = TestWorld.create(websiteURL: "/") + override func setUpWithError() throws { + testWorld = try TestWorld.create(websiteURL: "/") user = testWorld.createUser(username: "leia") } - override func tearDown() { - XCTAssertNoThrow(try testWorld.shutdown()) + override func tearDownWithError() throws { + try testWorld.shutdown() } // MARK: - Post Creation @@ -60,7 +60,7 @@ class AdminPostTests: XCTestCase { func testCreatingPostWithNonUniqueSlugFromSameTitle() throws { let randomNumber = 345 try testWorld.shutdown() - testWorld = TestWorld.create(randomNumberGenerator: StubbedRandomNumberGenerator(numberToReturn: randomNumber)) + testWorld = try TestWorld.create(randomNumberGenerator: StubbedRandomNumberGenerator(numberToReturn: randomNumber)) let initialPostData = try testWorld.createPost(title: "Post Title", slugUrl: "post-title") struct CreatePostData: Content { diff --git a/Tests/SteamPressTests/AdminTests/AdminUserTests.swift b/Tests/SteamPressTests/AdminTests/AdminUserTests.swift index 6aa9058c..3aeb8ea0 100644 --- a/Tests/SteamPressTests/AdminTests/AdminUserTests.swift +++ b/Tests/SteamPressTests/AdminTests/AdminUserTests.swift @@ -15,13 +15,13 @@ class AdminUserTests: XCTestCase { // MARK: - Overrides - override func setUp() { - testWorld = TestWorld.create() + override func setUpWithError() throws { + testWorld = try TestWorld.create() user = testWorld.createUser(name: "Leia", username: "leia") } - override func tearDown() { - XCTAssertNoThrow(try testWorld.shutdown()) + override func tearDownWithError() throws { + try testWorld.shutdown() } // MARK: - User Creation @@ -349,7 +349,7 @@ class AdminUserTests: XCTestCase { func testPasswordIsActuallyHashedWhenCreatingAUser() throws { try testWorld.shutdown() - testWorld = TestWorld.create(passwordHasherToUse: .reversed) + testWorld = try TestWorld.create(passwordHasherToUse: .reversed) let usersPassword = "password" let hashedPassword = String(usersPassword.reversed()) user = testWorld.createUser(name: "Leia", username: "leia", password: hashedPassword) @@ -639,7 +639,7 @@ class AdminUserTests: XCTestCase { func testPasswordIsActuallyHashedWhenEditingAUser() throws { try testWorld.shutdown() - testWorld = TestWorld.create(passwordHasherToUse: .reversed) + testWorld = try TestWorld.create(passwordHasherToUse: .reversed) let usersPassword = "password" let hashedPassword = String(usersPassword.reversed()) user = testWorld.createUser(name: "Leia", username: "leia", password: hashedPassword) @@ -726,7 +726,7 @@ class AdminUserTests: XCTestCase { func testCannotDeleteLastUser() throws { try testWorld.shutdown() - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let adminUser = testWorld.createUser(name: "Admin", username: "admin") let testData = try testWorld.createPost(author: adminUser) _ = try testWorld.getResponse(to: "/admin/users/\(adminUser.userID!)/delete", body: EmptyContent(), loggedInUser: adminUser) diff --git a/Tests/SteamPressTests/AdminTests/LoginTests.swift b/Tests/SteamPressTests/AdminTests/LoginTests.swift index 744c261b..ef5fd6f8 100644 --- a/Tests/SteamPressTests/AdminTests/LoginTests.swift +++ b/Tests/SteamPressTests/AdminTests/LoginTests.swift @@ -20,20 +20,20 @@ class LoginTests: XCTestCase { // MARK: - Overrides - override func setUp() { - testWorld = TestWorld.create(path: "blog", websiteURL: "/") + override func setUpWithError() throws { + testWorld = try TestWorld.create(path: "blog", websiteURL: "/") user = testWorld.createUser() } - override func tearDown() { - XCTAssertNoThrow(try testWorld.shutdown()) + override func tearDownWithError() throws { + try testWorld.shutdown() } // MARK: - Tests func testLogin() throws { try testWorld.shutdown() - testWorld = TestWorld.create(path: "blog", passwordHasherToUse: .real) + testWorld = try TestWorld.create(path: "blog", passwordHasherToUse: .real) let hashedPassword = try BCryptDigest().hash("password") user = testWorld.createUser(password: hashedPassword) let loginData = LoginData(username: user.username, password: "password") diff --git a/Tests/SteamPressTests/BlogTests/AuthorTests.swift b/Tests/SteamPressTests/BlogTests/AuthorTests.swift index a0914364..ac8cdc0a 100644 --- a/Tests/SteamPressTests/BlogTests/AuthorTests.swift +++ b/Tests/SteamPressTests/BlogTests/AuthorTests.swift @@ -17,15 +17,15 @@ class AuthorTests: XCTestCase { private var postsPerPage = 7 // MARK: - Overrides - - override func setUp() { - testWorld = TestWorld.create(postsPerPage: postsPerPage, websiteURL: "/") + + override func setUpWithError() throws { + testWorld = try TestWorld.create(postsPerPage: postsPerPage, websiteURL: "/") user = testWorld.createUser(username: "leia") - postData = try! testWorld.createPost(author: user) + postData = try testWorld.createPost(author: user) } - override func tearDown() { - XCTAssertNoThrow(try testWorld.shutdown()) + override func tearDownWithError() throws { + try testWorld.shutdown() } // MARK: - Tests @@ -54,7 +54,7 @@ class AuthorTests: XCTestCase { func testDisabledBlogAuthorsPath() throws { try testWorld.shutdown() - testWorld = TestWorld.create(enableAuthorPages: false) + testWorld = try TestWorld.create(enableAuthorPages: false) _ = testWorld.createUser(username: "leia") let authorResponse = try testWorld.getResponse(to: authorsRequestPath) diff --git a/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift b/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift index 1f863979..49117f42 100644 --- a/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift +++ b/Tests/SteamPressTests/BlogTests/DisabledBlogTagTests.swift @@ -3,7 +3,7 @@ import Vapor class DisabledBlogTagTests: XCTestCase { func testDisabledBlogTagsPath() throws { - let testWorld = TestWorld.create(enableTagPages: false) + let testWorld = try TestWorld.create(enableTagPages: false) _ = try testWorld.createTag("Engineering") var tagResponse: Response? = try testWorld.getResponse(to: "/tags/Engineering") var allTagsResponse: Response? = try testWorld.getResponse(to: "/tags") @@ -14,6 +14,6 @@ class DisabledBlogTagTests: XCTestCase { tagResponse = nil allTagsResponse = nil - XCTAssertNoThrow(try testWorld.shutdown()) + try testWorld.shutdown() } } diff --git a/Tests/SteamPressTests/BlogTests/IndexTests.swift b/Tests/SteamPressTests/BlogTests/IndexTests.swift index be56fd48..4a65a6b3 100644 --- a/Tests/SteamPressTests/BlogTests/IndexTests.swift +++ b/Tests/SteamPressTests/BlogTests/IndexTests.swift @@ -16,13 +16,13 @@ class IndexTests: XCTestCase { // MARK: - Overrides - override func setUp() { - testWorld = TestWorld.create(postsPerPage: postsPerPage, websiteURL: "/") - firstData = try! testWorld.createPost(title: "Test Path", slugUrl: "test-path") + override func setUpWithError() throws { + testWorld = try TestWorld.create(postsPerPage: postsPerPage, websiteURL: "/") + firstData = try testWorld.createPost(title: "Test Path", slugUrl: "test-path") } - override func tearDown() { - XCTAssertNoThrow(try testWorld.shutdown()) + override func tearDownWithError() throws { + try testWorld.shutdown() } // MARK: - Tests @@ -77,7 +77,7 @@ class IndexTests: XCTestCase { func testThatAccessingPathsRouteRedirectsToBlogIndexWithCustomPath() throws { try testWorld.shutdown() - testWorld = TestWorld.create(path: "blog") + testWorld = try TestWorld.create(path: "blog") let response = try testWorld.getResponse(to: "/blog/posts/") XCTAssertEqual(response.status, .movedPermanently) XCTAssertEqual(response.headers[.location].first, "/blog/") @@ -137,7 +137,7 @@ class IndexTests: XCTestCase { func testIndexPageCurrentPageWhenAtSubPath() throws { try testWorld.shutdown() - testWorld = TestWorld.create(path: "blog", websiteURL: "/") + testWorld = try TestWorld.create(path: "blog", websiteURL: "/") _ = try testWorld.getResponse(to: "/blog") XCTAssertEqual(presenter.indexPageInformation?.currentPageURL.absoluteString, "/blog") XCTAssertEqual(presenter.indexPageInformation?.websiteURL.absoluteString, "/") diff --git a/Tests/SteamPressTests/BlogTests/PostTests.swift b/Tests/SteamPressTests/BlogTests/PostTests.swift index 458c2541..b9874c20 100644 --- a/Tests/SteamPressTests/BlogTests/PostTests.swift +++ b/Tests/SteamPressTests/BlogTests/PostTests.swift @@ -17,12 +17,12 @@ class PostTests: XCTestCase { // MARK: - Overrides override func setUpWithError() throws { - testWorld = TestWorld.create(websiteURL: "/") + testWorld = try TestWorld.create(websiteURL: "/") firstData = try testWorld.createPost(title: "Test Path", slugUrl: "test-path") } - override func tearDown() { - XCTAssertNoThrow(try testWorld.shutdown()) + override func tearDownWithError() throws { + try testWorld.shutdown() } // MARK: - Tests diff --git a/Tests/SteamPressTests/BlogTests/SearchTests.swift b/Tests/SteamPressTests/BlogTests/SearchTests.swift index c2d0dc55..daa1abbd 100644 --- a/Tests/SteamPressTests/BlogTests/SearchTests.swift +++ b/Tests/SteamPressTests/BlogTests/SearchTests.swift @@ -16,12 +16,12 @@ class SearchTests: XCTestCase { // MARK: - Overrides override func setUpWithError() throws { - testWorld = TestWorld.create(websiteURL: "/") + testWorld = try TestWorld.create(websiteURL: "/") firstData = try testWorld.createPost(title: "Test Path", slugUrl: "test-path") } - override func tearDown() { - XCTAssertNoThrow(try testWorld.shutdown()) + override func tearDownWithError() throws { + try testWorld.shutdown() } // MARK: - Tests diff --git a/Tests/SteamPressTests/BlogTests/TagTests.swift b/Tests/SteamPressTests/BlogTests/TagTests.swift index 78df2f83..850682a5 100644 --- a/Tests/SteamPressTests/BlogTests/TagTests.swift +++ b/Tests/SteamPressTests/BlogTests/TagTests.swift @@ -19,21 +19,21 @@ class TagTests: XCTestCase { // MARK: - Overrides - override func setUp() { - testWorld = TestWorld.create(postsPerPage: postsPerPage, websiteURL: "/") - postData = try! testWorld.createPost() - tag = try! testWorld.createTag(tagName, on: postData.post) + override func setUpWithError() throws { + testWorld = try TestWorld.create(postsPerPage: postsPerPage, websiteURL: "/") + postData = try testWorld.createPost() + tag = try testWorld.createTag(tagName, on: postData.post) } - override func tearDown() { - XCTAssertNoThrow(try testWorld.shutdown()) + override func tearDownWithError() throws { + try testWorld.shutdown() } // MARK: - Tests func testAllTagsPageGetsAllTags() throws { - let secondPost = try! testWorld.createPost() - let thirdPost = try! testWorld.createPost() + let secondPost = try testWorld.createPost() + let thirdPost = try testWorld.createPost() let secondTag = try testWorld.createTag("AnotherTag", on: secondPost.post) try testWorld.context.repository.internalAdd(secondTag, to: thirdPost.post) _ = try testWorld.getResponse(to: allTagsRequestPath) diff --git a/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift b/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift index 99202d04..e0a13b74 100644 --- a/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift +++ b/Tests/SteamPressTests/Feed Tests/AtomFeedTests.swift @@ -18,14 +18,14 @@ class AtomFeedTests: XCTestCase { dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) } - override func tearDown() { - XCTAssertNoThrow(try testWorld.shutdown()) + override func tearDownWithError() throws { + try testWorld.shutdown() } // MARK: - Tests func testNoPostsReturnsCorrectAtomFeed() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" @@ -37,7 +37,7 @@ class AtomFeedTests: XCTestCase { func testThatFeedTitleCanBeConfigured() throws { let title = "My Awesome Blog" let feedInformation = FeedInformation(title: title) - testWorld = TestWorld.create(feedInformation: feedInformation) + testWorld = try TestWorld.create(feedInformation: feedInformation) let expectedXML = "\n\n\n\(title)\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" @@ -48,7 +48,7 @@ class AtomFeedTests: XCTestCase { func testThatFeedSubtitleCanBeConfigured() throws { let description = "This is a test for my blog" let feedInformation = FeedInformation(description: description) - testWorld = TestWorld.create(feedInformation: feedInformation) + testWorld = try TestWorld.create(feedInformation: feedInformation) let expectedXML = "\n\n\nSteamPress Blog\n\(description)\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n" @@ -59,7 +59,7 @@ class AtomFeedTests: XCTestCase { func testThatRightsCanBeConifgured() throws { let copyright = "Copyright ©️ 2019 SteamPress" let feedInformation = FeedInformation(copyright: copyright) - testWorld = TestWorld.create(feedInformation: feedInformation) + testWorld = try TestWorld.create(feedInformation: feedInformation) let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\(copyright)\n" @@ -70,7 +70,7 @@ class AtomFeedTests: XCTestCase { func testThatLogoCanBeConfigured() throws { let imageURL = "https://static.brokenhands.io/images/feeds/atom.png" let feedInformation = FeedInformation(imageURL: imageURL) - testWorld = TestWorld.create(feedInformation: feedInformation) + testWorld = try TestWorld.create(feedInformation: feedInformation) let expectedXML = "\n\n\nSteamPress Blog\nSteamPress is an open-source blogging engine written for Vapor in Swift\nhttps://www.steampress.io/\n\n\nSteamPress\n\(dateFormatter.string(from: Date()))\n\(imageURL)\n" let actualXmlResponse = try testWorld.getResponseString(to: atomPath) @@ -78,7 +78,7 @@ class AtomFeedTests: XCTestCase { } func testThatFeedIsCorrectForOnePost() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let testData = try testWorld.createPost() let post = testData.post @@ -91,7 +91,7 @@ class AtomFeedTests: XCTestCase { } func testThatFeedIsCorrectForOnePostUnderPath() throws { - testWorld = TestWorld.create(path: "blog") + testWorld = try TestWorld.create(path: "blog") let testData = try testWorld.createPost() let post = testData.post @@ -104,7 +104,7 @@ class AtomFeedTests: XCTestCase { } func testThatFeedCorrectForTwoPosts() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let testData = try testWorld.createPost() let post = testData.post @@ -121,7 +121,7 @@ class AtomFeedTests: XCTestCase { } func testThatDraftsDontAppearInFeed() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let testData = try testWorld.createPost() let post = testData.post @@ -136,7 +136,7 @@ class AtomFeedTests: XCTestCase { } func testThatEditedPostsHaveUpdatedTimes() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let firstPostDate = Date().addingTimeInterval(-3600) let testData = try testWorld.createPost(createdDate: firstPostDate) @@ -156,7 +156,7 @@ class AtomFeedTests: XCTestCase { } func testThatTagsAppearWhenPostHasThem() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let tag1 = "Vapor 2" let tag2 = "Engineering" @@ -172,13 +172,13 @@ class AtomFeedTests: XCTestCase { } func testCorrectHeaderSetForAtomFeed() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let actualXmlResponse = try testWorld.getResponse(to: atomPath) XCTAssertEqual(actualXmlResponse.headers.first(name: .contentType), "application/atom+xml") } func testThatDateFormatterIsCorrect() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let createDate = Date(timeIntervalSince1970: 1505867108) let testData = try testWorld.createPost(createdDate: createDate) diff --git a/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift b/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift index 3fd4c4fb..1a9ee98a 100644 --- a/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift +++ b/Tests/SteamPressTests/Feed Tests/RSSFeedTests.swift @@ -18,14 +18,14 @@ class RSSFeedTests: XCTestCase { dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) } - override func tearDown() { - XCTAssertNoThrow(try testWorld.shutdown()) + override func tearDownWithError() throws { + try testWorld.shutdown() } // MARK: - Tests func testNoPostsReturnsCorrectRSSFeed() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n" @@ -34,7 +34,7 @@ class RSSFeedTests: XCTestCase { } func testOnePostReturnsCorrectRSSFeed() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let testData = try testWorld.createPost() let post = testData.post let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(dateFormatter.string(from: post.created))\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n\(post.title)\n\n\n\(try post.description())\n\n\nhttps://www.steampress.io/posts/\(post.slugUrl)/\n\n\(dateFormatter.string(from: post.created))\n\n\n\n" @@ -44,7 +44,7 @@ class RSSFeedTests: XCTestCase { } func testMultiplePostsReturnsCorrectRSSFeed() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let testData = try testWorld.createPost() let post = testData.post let author = testData.author @@ -60,7 +60,7 @@ class RSSFeedTests: XCTestCase { } func testDraftsAreNotIncludedInFeed() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let testData = try testWorld.createPost() let post = testData.post @@ -75,7 +75,7 @@ class RSSFeedTests: XCTestCase { func testBlogTitleCanBeConfigured() throws { let title = "SteamPress - The Open Source Blog" let feedInformation = FeedInformation(title: title) - testWorld = TestWorld.create(feedInformation: feedInformation) + testWorld = try TestWorld.create(feedInformation: feedInformation) let testData = try testWorld.createPost() let post = testData.post @@ -89,7 +89,7 @@ class RSSFeedTests: XCTestCase { func testBlogDescriptionCanBeConfigured() throws { let description = "Our fancy new RSS-feed blog" let feedInformation = FeedInformation(description: description) - testWorld = TestWorld.create(feedInformation: feedInformation) + testWorld = try TestWorld.create(feedInformation: feedInformation) let testData = try testWorld.createPost() let post = testData.post @@ -101,7 +101,7 @@ class RSSFeedTests: XCTestCase { } func testRSSFeedEndpointAddedToCorrectEndpointWhenBlogInSubPath() throws { - testWorld = TestWorld.create(path: "blog-path") + testWorld = try TestWorld.create(path: "blog-path") let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/blog-path/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/blog-path/search?\nterm\n\n\n\n" @@ -110,7 +110,7 @@ class RSSFeedTests: XCTestCase { } func testPostLinkWhenBlogIsPlacedAtSubPath() throws { - testWorld = TestWorld.create(path: "blog-path") + testWorld = try TestWorld.create(path: "blog-path") let testData = try testWorld.createPost() let post = testData.post @@ -123,7 +123,7 @@ class RSSFeedTests: XCTestCase { func testCopyrightCanBeAddedToRSS() throws { let copyright = "Copyright ©️ 2017 SteamPress" let feedInformation = FeedInformation(copyright: copyright) - testWorld = TestWorld.create(feedInformation: feedInformation) + testWorld = try TestWorld.create(feedInformation: feedInformation) let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\(copyright)\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n" @@ -132,7 +132,7 @@ class RSSFeedTests: XCTestCase { } func testThatTagsAreAddedToPostCorrectly() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let testData = try testWorld.createPost(tags: ["Vapor 2", "Engineering"]) let post = testData.post @@ -145,7 +145,7 @@ class RSSFeedTests: XCTestCase { func testImageIsProvidedIfSupplied() throws { let image = "https://static.brokenhands.io/images/brokenhands.png" let feedInformation = FeedInformation(imageURL: image) - testWorld = TestWorld.create(feedInformation: feedInformation) + testWorld = try TestWorld.create(feedInformation: feedInformation) let expectedXML = "\n\n\n\nSteamPress Blog\nhttps://www.steampress.io/\nSteamPress is an open-source blogging engine written for Vapor in Swift\nSteamPress\n60\n\n\(image)\nSteamPress Blog\nhttps://www.steampress.io/\n\n\nSearch SteamPress Blog\nSearch\nhttps://www.steampress.io/search?\nterm\n\n\n\n" @@ -154,7 +154,7 @@ class RSSFeedTests: XCTestCase { } func testCorrectHeaderSetForRSSFeed() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let actualXmlResponse = try testWorld.getResponse(to: rssPath) XCTAssertEqual(actualXmlResponse.headers.first(name: .contentType), "application/rss+xml") @@ -162,7 +162,7 @@ class RSSFeedTests: XCTestCase { func testThatDateFormatterIsCorrect() throws { let createDate = Date(timeIntervalSince1970: 1505867108) - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let testData = try testWorld.createPost(createdDate: createDate) let post = testData.post @@ -173,7 +173,7 @@ class RSSFeedTests: XCTestCase { } func testThatDescriptionContainsOnlyText() throws { - testWorld = TestWorld.create() + testWorld = try TestWorld.create() let contents = "[This is](https://www.google.com) a post that contains some **text**. \n# Formatting should be removed" let testData = try testWorld.createPost(contents: contents) let post = testData.post diff --git a/Tests/SteamPressTests/Helpers/TestWorld.swift b/Tests/SteamPressTests/Helpers/TestWorld.swift index 0ee5c935..ab0f69bc 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld.swift @@ -3,7 +3,7 @@ import Vapor struct TestWorld { - static func create(path: String? = nil, postsPerPage: Int = 10, feedInformation: FeedInformation = FeedInformation(), enableAuthorPages: Bool = true, enableTagPages: Bool = true, passwordHasherToUse: PasswordHasherChoice = .plaintext, randomNumberGenerator: StubbedRandomNumberGenerator = StubbedRandomNumberGenerator(numberToReturn: 666), websiteURL: String = "https://www.steampress.io") -> TestWorld { + static func create(path: String? = nil, postsPerPage: Int = 10, feedInformation: FeedInformation = FeedInformation(), enableAuthorPages: Bool = true, enableTagPages: Bool = true, passwordHasherToUse: PasswordHasherChoice = .plaintext, randomNumberGenerator: StubbedRandomNumberGenerator = StubbedRandomNumberGenerator(numberToReturn: 666), websiteURL: String = "https://www.steampress.io") throws -> TestWorld { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) let repository = InMemoryRepository(eventLoop: eventLoopGroup.next()) let blogPresenter = CapturingBlogPresenter(eventLoop: eventLoopGroup.next()) @@ -15,7 +15,7 @@ struct TestWorld { unsetenv("BLOG_DISQUS_NAME") unsetenv("WEBSITE_URL") setenv("WEBSITE_URL", websiteURL, 1) - try! application.boot() + try application.boot() return TestWorld(context: context) } diff --git a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift index 8c7b9c96..b1a45f04 100644 --- a/Tests/SteamPressTests/ViewTests/BlogViewTests.swift +++ b/Tests/SteamPressTests/ViewTests/BlogViewTests.swift @@ -16,14 +16,14 @@ class BlogViewTests: XCTestCase { // MARK: - Overrides - override func setUp() { + override func setUpWithError() throws { eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) viewRenderer = CapturingViewRenderer(eventLoop: eventLoopGroup.next()) presenter = ViewBlogPresenter(viewRenderer: viewRenderer, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: eventLoopGroup) author = TestDataBuilder.anyUser(id: 1) let createdDate = Date(timeIntervalSince1970: 1584714638) let lastEditedDate = Date(timeIntervalSince1970: 1584981458) - post = try! TestDataBuilder.anyPost(author: author, contents: TestDataBuilder.longContents, creationDate: createdDate, lastEditedDate: lastEditedDate) + post = try TestDataBuilder.anyPost(author: author, contents: TestDataBuilder.longContents, creationDate: createdDate, lastEditedDate: lastEditedDate) websiteURL = URL(string: "https://www.brokenhands.io")! currentPageURL = websiteURL.appendingPathComponent("blog").appendingPathComponent("posts").appendingPathComponent("test-post") pageInformation = BlogGlobalPageInformation(disqusName: "disqusName", siteTwitterHandle: "twitterHandleSomething", googleAnalyticsIdentifier: "GAString....", loggedInUser: author, websiteURL: websiteURL, currentPageURL: currentPageURL, currentPageEncodedURL: currentPageURL.absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!) From 416365ab527b0316418ea9b1355e441783f99579 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Tue, 14 Apr 2020 15:47:29 +0100 Subject: [PATCH 63/70] Remove commented out code --- README.md | 8 ++++---- .../Helpers/TestWorld+Application.swift | 11 +---------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 294b93aa..239dc12b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

- Language + Language Build Status @@ -16,7 +16,7 @@

-SteamPress is a Swift blogging engine for use with the Vapor Framework to deploy blogs to sites that run on top of Vapor. It uses [Fluent](https://github.com/vapor/fluent) so will work with any database that has a Fluent Driver. It also incorporates a [Markdown Provider](https://github.com/vapor-community/markdown-provider) allowing you to write your posts in Markdown and then use Leaf to render the markdown. +SteamPress is a Swift blogging engine for use with the Vapor Framework to deploy blogs to sites that run on top of Vapor. It uses protocols to define database storage, so will work with any database that has a `SteamPressRepository` implementation, or you can write your own! It also incorporates a [Markdown Provider](https://github.com/vapor-community/markdown-provider) allowing you to write your posts in Markdown and then use Leaf to render the markdown. The blog can either be used as the root of your website (i.e. appearing at https://www.acme.org) or in a subpath (i.e. https://www.acme.org/blog/). @@ -49,7 +49,7 @@ First, add the provider to your `Package.swift` dependencies: ```swift dependencies: [ // ... - .package(name: "SteampressFluentPostgres", url: "https://github.com/brokenhandsio/steampress-fluent-postgres.git", from: "1.0.0"), + .package(name: "SteampressFluentPostgres", url: "https://github.com/brokenhandsio/steampress-fluent-postgres.git", from: "2.0.0"), ], ``` @@ -98,7 +98,7 @@ First add SteamPress to your `Package.swift` dependencies: ```swift dependencies: [ // ..., - .package(name: "SteamPress", url: "https://github.com/brokenhandsio/SteamPress", from: "1.0.0") + .package(name: "SteamPress", url: "https://github.com/brokenhandsio/SteamPress", from: "2.0.0") ] ``` diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift index cde7658e..b84ca3a8 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift @@ -16,17 +16,8 @@ extension TestWorld { let application = Application(.testing, .shared(eventLoopGroup)) -// let steampress = SteamPress.SteampressLifecyle( -// blogPath: path, -// feedInformation: feedInformation, -// postsPerPage: postsPerPage, -// enableAuthorPages: enableAuthorPages, -// enableTagPages: enableTagPages) application.steampress.configuration = SteamPressConfiguration(blogPath: path, feedInformation: feedInformation, postsPerPage: postsPerPage, enableAuthorPages: enableAuthorPages, enableTagPages: enableTagPages) -// application.lifecycle.use(steampress) -// -// #warning("This should be removed") -// steampress.tmpSetup(application) + application.blogRepositories.use { _ in return repository } From f7aed38e49a510cc26a46d65856916e0d346a147 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Tue, 14 Apr 2020 16:06:35 +0100 Subject: [PATCH 64/70] Namespace all the steampress stuff on application --- ...cation+SteamPress+BlogAdminPresenter.swift | 75 ++++++++++++++++ ...Application+SteamPress+BlogPresenter.swift | 71 ++++++++++++++++ .../Presenters/BlogAdminPresenter.swift | 74 ---------------- .../SteamPress/Presenters/BlogPresenter.swift | 70 --------------- .../Application+SteamPress+Repositories.swift | 72 ++++++++++++++++ .../Repositories/Request+Repositories.swift | 15 ++++ .../Repositories/SteamPressRepository.swift | 85 ------------------- .../SteamPressRandomNumberGenerator.swift | 8 +- .../Helpers/TestWorld+Application.swift | 8 +- Tests/SteamPressTests/ProviderTests.swift | 8 +- 10 files changed, 245 insertions(+), 241 deletions(-) create mode 100644 Sources/SteamPress/Presenters/Application+SteamPress+BlogAdminPresenter.swift create mode 100644 Sources/SteamPress/Presenters/Application+SteamPress+BlogPresenter.swift create mode 100644 Sources/SteamPress/Repositories/Application+SteamPress+Repositories.swift create mode 100644 Sources/SteamPress/Repositories/Request+Repositories.swift diff --git a/Sources/SteamPress/Presenters/Application+SteamPress+BlogAdminPresenter.swift b/Sources/SteamPress/Presenters/Application+SteamPress+BlogAdminPresenter.swift new file mode 100644 index 00000000..6ed7c8c9 --- /dev/null +++ b/Sources/SteamPress/Presenters/Application+SteamPress+BlogAdminPresenter.swift @@ -0,0 +1,75 @@ +import Vapor + +extension Request { + var adminPresenter: BlogAdminPresenter { + self.application.steampress.adminPresenters.adminPresenter.for(self, pathCreator: self.application.steampress.adminPresenters.storage.pathCreator) + } +} + +extension Application.SteamPress { + struct BlogAdminPresenters { + struct Provider { + static var view: Self { + .init { + $0.steampress.adminPresenters.use { $0.steampress.adminPresenters.view } + } + } + + let run: (Application) -> () + + init(_ run: @escaping (Application) -> ()) { + self.run = run + } + } + + final class Storage { + let pathCreator: BlogPathCreator + var makePresenter: ((Application) -> BlogAdminPresenter)? + init(pathCreator: BlogPathCreator) { + self.pathCreator = pathCreator + } + } + + struct Key: StorageKey { + typealias Value = Storage + } + + let application: Application + + var view: ViewBlogAdminPresenter { + return .init(pathCreator: self.storage.pathCreator, viewRenderer: self.application.view, eventLoopGroup: self.application.eventLoopGroup, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) + } + + var adminPresenter: BlogAdminPresenter { + guard let makePresenter = self.storage.makePresenter else { + fatalError("No blog admin presenter configured. Configure with app.adminPresenters.use(...)") + } + return makePresenter(self.application) + } + + func use(_ provider: Provider) { + provider.run(self.application) + } + + func use(_ makePresenter: @escaping (Application) -> (BlogAdminPresenter)) { + self.storage.makePresenter = makePresenter + } + + func initialize(pathCreator: BlogPathCreator) { + self.application.storage[Key.self] = .init(pathCreator: pathCreator) + self.use(.view) + } + + var storage: Storage { + if self.application.storage[Key.self] == nil { + let pathCreator = BlogPathCreator(blogPath: self.application.steampress.configuration.blogPath) + initialize(pathCreator: pathCreator) + } + return self.application.storage[Key.self]! + } + } + + var adminPresenters: BlogAdminPresenters { + .init(application: self.application) + } +} diff --git a/Sources/SteamPress/Presenters/Application+SteamPress+BlogPresenter.swift b/Sources/SteamPress/Presenters/Application+SteamPress+BlogPresenter.swift new file mode 100644 index 00000000..b6ade71b --- /dev/null +++ b/Sources/SteamPress/Presenters/Application+SteamPress+BlogPresenter.swift @@ -0,0 +1,71 @@ +import Vapor + +extension Request { + var blogPresenter: BlogPresenter { + self.application.steampress.blogPresenters.blogPresenter.for(self) + } +} + +extension Application.SteamPress { + struct BlogPresenters { + struct Provider { + static var view: Self { + .init { + $0.steampress.blogPresenters.use { $0.steampress.blogPresenters.view } + } + } + + let run: (Application) -> () + + public init(_ run: @escaping (Application) -> ()) { + self.run = run + } + } + + final class Storage { + var makePresenter: ((Application) -> BlogPresenter)? + init() { } + } + + struct Key: StorageKey { + typealias Value = Storage + } + + let application: Application + + var view: ViewBlogPresenter { + return .init(viewRenderer: self.application.view, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: self.application.eventLoopGroup) + } + + var blogPresenter: BlogPresenter { + guard let makePresenter = self.storage.makePresenter else { + fatalError("No blog presenter configured. Configure with app.blogPresenters.use(...)") + } + return makePresenter(self.application) + } + + func use(_ provider: Provider) { + provider.run(self.application) + } + + func use(_ makePresenter: @escaping (Application) -> (BlogPresenter)) { + self.storage.makePresenter = makePresenter + } + + func initialize() { + self.application.storage[Key.self] = .init() + self.use(.view) + } + + private var storage: Storage { + if self.application.storage[Key.self] == nil { + self.initialize() + } + return self.application.storage[Key.self]! + } + } + + var blogPresenters: BlogPresenters { + .init(application: self.application) + } +} diff --git a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift index 5c288f55..40edbc5f 100644 --- a/Sources/SteamPress/Presenters/BlogAdminPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogAdminPresenter.swift @@ -13,77 +13,3 @@ extension ViewBlogAdminPresenter { return ViewBlogAdminPresenter(pathCreator: pathCreator, viewRenderer: request.view, eventLoopGroup: request.eventLoop, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) } } - -extension Request { - var adminPresenter: BlogAdminPresenter { - self.application.adminPresenters.adminPresenter.for(self, pathCreator: self.application.adminPresenters.storage.pathCreator) - } -} - -extension Application { - struct BlogAdminPresenters { - struct Provider { - static var view: Self { - .init { - $0.adminPresenters.use { $0.adminPresenters.view } - } - } - - let run: (Application) -> () - - init(_ run: @escaping (Application) -> ()) { - self.run = run - } - } - - final class Storage { - let pathCreator: BlogPathCreator - var makePresenter: ((Application) -> BlogAdminPresenter)? - init(pathCreator: BlogPathCreator) { - self.pathCreator = pathCreator - } - } - - struct Key: StorageKey { - typealias Value = Storage - } - - let application: Application - - var view: ViewBlogAdminPresenter { - return .init(pathCreator: self.storage.pathCreator, viewRenderer: self.application.view, eventLoopGroup: self.application.eventLoopGroup, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter()) - } - - var adminPresenter: BlogAdminPresenter { - guard let makePresenter = self.storage.makePresenter else { - fatalError("No blog admin presenter configured. Configure with app.adminPresenters.use(...)") - } - return makePresenter(self.application) - } - - func use(_ provider: Provider) { - provider.run(self.application) - } - - func use(_ makePresenter: @escaping (Application) -> (BlogAdminPresenter)) { - self.storage.makePresenter = makePresenter - } - - func initialize(pathCreator: BlogPathCreator) { - self.application.storage[Key.self] = .init(pathCreator: pathCreator) - self.use(.view) - } - - var storage: Storage { - if self.application.storage[Key.self] == nil { - let pathCreator = BlogPathCreator(blogPath: self.application.steampress.configuration.blogPath) - initialize(pathCreator: pathCreator) - } - return self.application.storage[Key.self]! - } - } - - var adminPresenters: BlogAdminPresenters { - .init(application: self) - } -} diff --git a/Sources/SteamPress/Presenters/BlogPresenter.swift b/Sources/SteamPress/Presenters/BlogPresenter.swift index 7270a960..9f3b6a75 100644 --- a/Sources/SteamPress/Presenters/BlogPresenter.swift +++ b/Sources/SteamPress/Presenters/BlogPresenter.swift @@ -17,73 +17,3 @@ extension ViewBlogPresenter { return ViewBlogPresenter(viewRenderer: request.view, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: request.eventLoop) } } - -extension Request { - var blogPresenter: BlogPresenter { - self.application.blogPresenters.blogPresenter.for(self) - } -} - -extension Application { - struct BlogPresenters { - struct Provider { - static var view: Self { - .init { - $0.blogPresenters.use { $0.blogPresenters.view } - } - } - - let run: (Application) -> () - - public init(_ run: @escaping (Application) -> ()) { - self.run = run - } - } - - final class Storage { - var makePresenter: ((Application) -> BlogPresenter)? - init() { } - } - - struct Key: StorageKey { - typealias Value = Storage - } - - let application: Application - - var view: ViewBlogPresenter { - return .init(viewRenderer: self.application.view, longDateFormatter: LongPostDateFormatter(), numericDateFormatter: NumericPostDateFormatter(), eventLoopGroup: self.application.eventLoopGroup) - } - - var blogPresenter: BlogPresenter { - guard let makePresenter = self.storage.makePresenter else { - fatalError("No blog presenter configured. Configure with app.blogPresenters.use(...)") - } - return makePresenter(self.application) - } - - func use(_ provider: Provider) { - provider.run(self.application) - } - - func use(_ makePresenter: @escaping (Application) -> (BlogPresenter)) { - self.storage.makePresenter = makePresenter - } - - func initialize() { - self.application.storage[Key.self] = .init() - self.use(.view) - } - - private var storage: Storage { - if self.application.storage[Key.self] == nil { - self.initialize() - } - return self.application.storage[Key.self]! - } - } - - var blogPresenters: BlogPresenters { - .init(application: self) - } -} diff --git a/Sources/SteamPress/Repositories/Application+SteamPress+Repositories.swift b/Sources/SteamPress/Repositories/Application+SteamPress+Repositories.swift new file mode 100644 index 00000000..2ed49dee --- /dev/null +++ b/Sources/SteamPress/Repositories/Application+SteamPress+Repositories.swift @@ -0,0 +1,72 @@ +import Vapor + +public extension Application.SteamPress { + struct BlogRepositories { + public struct Provider { + let run: (Application) -> () + + public init(_ run: @escaping (Application) -> ()) { + self.run = run + } + } + + final class Storage { + var makePostRepository: ((Application) -> BlogPostRepository)? + var makeTagRepository: ((Application) -> BlogTagRepository)? + var makeUserRepository: ((Application) -> BlogUserRepository)? + init() { } + } + + struct Key: StorageKey { + typealias Value = Storage + } + + let application: Application + + var userRepository: BlogUserRepository { + guard let makeRepository = self.storage.makeUserRepository else { + fatalError("No user repository configured. Configure with app.blogRepositories.use(...)") + } + return makeRepository(self.application) + } + + var postRepository: BlogPostRepository { + guard let makeRepository = self.storage.makePostRepository else { + fatalError("No post repository configured. Configure with app.blogRepositories.use(...)") + } + return makeRepository(self.application) + } + + var tagRepository: BlogTagRepository { + guard let makeRepository = self.storage.makeTagRepository else { + fatalError("No tag repository configured. Configure with app.blogRepositories.use(...)") + } + return makeRepository(self.application) + } + + public func use(_ provider: Provider) { + provider.run(self.application) + } + + public func use(_ makeRespository: @escaping (Application) -> (BlogUserRepository & BlogTagRepository & BlogPostRepository)) { + self.storage.makeUserRepository = makeRespository + self.storage.makeTagRepository = makeRespository + self.storage.makePostRepository = makeRespository + } + + public func initialize() { + self.application.storage[Key.self] = .init() + } + + private var storage: Storage { + if self.application.storage[Key.self] == nil { + self.initialize() + } + return self.application.storage[Key.self]! + } + } + + var blogRepositories: BlogRepositories { + .init(application: self.application) + } +} diff --git a/Sources/SteamPress/Repositories/Request+Repositories.swift b/Sources/SteamPress/Repositories/Request+Repositories.swift new file mode 100644 index 00000000..b92feae0 --- /dev/null +++ b/Sources/SteamPress/Repositories/Request+Repositories.swift @@ -0,0 +1,15 @@ +import Vapor + +public extension Request { + var blogUserRepository: BlogUserRepository { + self.application.steampress.blogRepositories.userRepository.for(self) + } + + var blogPostRepository: BlogPostRepository { + self.application.steampress.blogRepositories.postRepository.for(self) + } + + var blogTagRepository: BlogTagRepository { + self.application.steampress.blogRepositories.tagRepository.for(self) + } +} diff --git a/Sources/SteamPress/Repositories/SteamPressRepository.swift b/Sources/SteamPress/Repositories/SteamPressRepository.swift index 90ee244a..436c1262 100644 --- a/Sources/SteamPress/Repositories/SteamPressRepository.swift +++ b/Sources/SteamPress/Repositories/SteamPressRepository.swift @@ -47,88 +47,3 @@ public protocol BlogUserRepository: SteamPressRepository { func delete(_ user: BlogUser) -> EventLoopFuture func getUsersCount() -> EventLoopFuture } - -public extension Request { - var blogUserRepository: BlogUserRepository { - self.application.blogRepositories.userRepository.for(self) - } - - var blogPostRepository: BlogPostRepository { - self.application.blogRepositories.postRepository.for(self) - } - - var blogTagRepository: BlogTagRepository { - self.application.blogRepositories.tagRepository.for(self) - } -} - -public extension Application { - struct BlogRepositories { - public struct Provider { - let run: (Application) -> () - - public init(_ run: @escaping (Application) -> ()) { - self.run = run - } - } - - final class Storage { - var makePostRepository: ((Application) -> BlogPostRepository)? - var makeTagRepository: ((Application) -> BlogTagRepository)? - var makeUserRepository: ((Application) -> BlogUserRepository)? - init() { } - } - - struct Key: StorageKey { - typealias Value = Storage - } - - let application: Application - - var userRepository: BlogUserRepository { - guard let makeRepository = self.storage.makeUserRepository else { - fatalError("No user repository configured. Configure with app.blogRepositories.use(...)") - } - return makeRepository(self.application) - } - - var postRepository: BlogPostRepository { - guard let makeRepository = self.storage.makePostRepository else { - fatalError("No post repository configured. Configure with app.blogRepositories.use(...)") - } - return makeRepository(self.application) - } - - var tagRepository: BlogTagRepository { - guard let makeRepository = self.storage.makeTagRepository else { - fatalError("No tag repository configured. Configure with app.blogRepositories.use(...)") - } - return makeRepository(self.application) - } - - public func use(_ provider: Provider) { - provider.run(self.application) - } - - public func use(_ makeRespository: @escaping (Application) -> (BlogUserRepository & BlogTagRepository & BlogPostRepository)) { - self.storage.makeUserRepository = makeRespository - self.storage.makeTagRepository = makeRespository - self.storage.makePostRepository = makeRespository - } - - public func initialize() { - self.application.storage[Key.self] = .init() - } - - private var storage: Storage { - if self.application.storage[Key.self] == nil { - self.initialize() - } - return self.application.storage[Key.self]! - } - } - - var blogRepositories: BlogRepositories { - .init(application: self) - } -} diff --git a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift index 7d75bdb0..ce1753db 100644 --- a/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift +++ b/Sources/SteamPress/Services/SteamPressRandomNumberGenerator.swift @@ -13,16 +13,16 @@ extension RealRandomNumberGenerator { public extension Request { var randomNumberGenerator: SteamPressRandomNumberGenerator { - self.application.randomNumberGenerators.generator.for(self) + self.application.steampress.randomNumberGenerators.generator.for(self) } } -public extension Application { +public extension Application.SteamPress { struct RandomNumberGenerators { public struct Provider { static var real: Self { .init { - $0.randomNumberGenerators.use { $0.randomNumberGenerators.real } + $0.steampress.randomNumberGenerators.use { $0.steampress.randomNumberGenerators.real } } } @@ -77,6 +77,6 @@ public extension Application { } var randomNumberGenerators: RandomNumberGenerators { - .init(application: self) + .init(application: self.application) } } diff --git a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift index b84ca3a8..2c967974 100644 --- a/Tests/SteamPressTests/Helpers/TestWorld+Application.swift +++ b/Tests/SteamPressTests/Helpers/TestWorld+Application.swift @@ -18,19 +18,19 @@ extension TestWorld { application.steampress.configuration = SteamPressConfiguration(blogPath: path, feedInformation: feedInformation, postsPerPage: postsPerPage, enableAuthorPages: enableAuthorPages, enableTagPages: enableTagPages) - application.blogRepositories.use { _ in + application.steampress.blogRepositories.use { _ in return repository } - application.randomNumberGenerators.use { _ in randomNumberGenerator } + application.steampress.randomNumberGenerators.use { _ in randomNumberGenerator } application.middleware.use(BlogRememberMeMiddleware()) application.middleware.use(SessionsMiddleware(session: application.sessions.driver)) - application.blogPresenters.use { _ in + application.steampress.blogPresenters.use { _ in return blogPresenter } - application.adminPresenters.use { _ in + application.steampress.adminPresenters.use { _ in return adminPresenter } diff --git a/Tests/SteamPressTests/ProviderTests.swift b/Tests/SteamPressTests/ProviderTests.swift index 61b8d2df..877ea828 100644 --- a/Tests/SteamPressTests/ProviderTests.swift +++ b/Tests/SteamPressTests/ProviderTests.swift @@ -8,17 +8,17 @@ class ProviderTests: XCTestCase { app.middleware.use(BlogRememberMeMiddleware()) app.middleware.use(SessionsMiddleware(session: app.sessions.driver)) - app.blogRepositories.use { application in + app.steampress.blogRepositories.use { application in return InMemoryRepository(eventLoop: application.eventLoopGroup.next()) } - let numberGenerator = app.randomNumberGenerators.generator + let numberGenerator = app.steampress.randomNumberGenerators.generator XCTAssertTrue(type(of: numberGenerator) == RealRandomNumberGenerator.self) - let blogPresenter = app.blogPresenters.blogPresenter + let blogPresenter = app.steampress.blogPresenters.blogPresenter XCTAssertTrue(type(of: blogPresenter) == ViewBlogPresenter.self) - let blogAdminPresenter = app.adminPresenters.adminPresenter + let blogAdminPresenter = app.steampress.adminPresenters.adminPresenter XCTAssertTrue(type(of: blogAdminPresenter) == ViewBlogAdminPresenter.self) app.shutdown() From 4ca3b12f13fcc058ab39f5c4f636310b73b760bb Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Tue, 14 Apr 2020 16:11:25 +0100 Subject: [PATCH 65/70] Update README for Vapor 4 --- README.md | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 239dc12b..44798305 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ There is an example of how it can work in a site (and what it requires in terms ## Add as a dependency +**TODO** Update + SteamPress is easy to integrate with your application. There are two providers that provide implementations for [PostgreSQL](https://github.com/brokenhandsio/steampress-fluent-postgres) or [MySQL](https://github.com/brokenhandsio/steampress-fluent-mysql). You are also free to write your own integrations. Normally you'd choose one of the implementations as that provides repository integrations for the database. In this example, we're using Postgres. First, add the provider to your `Package.swift` dependencies: @@ -115,42 +117,29 @@ And then as a dependency to your target: This will register the routes for you. You must provide implementations for the different repository types to your services: ```swift -services.register(MyTagRepository(), as: BlogTagRepository.self) -services.register(MyUserRepository(), as: BlogUserRepository.self) -services.register(MyPostRepository(), as: BlogPostRepository.self) +app.steampress.blogRepositories.use { application in + MyRepository(application: application) +} ``` -You can then register the SteamPress provider with your services: - -```swift -let steampressProvider = SteamPress.Provider() -try services.register(steampressProvider) -``` +SteamPress will be automatically registered, depending on the configuration provided (see below). ## Integration SteamPress offers a 'Remember Me' functionality when logging in to extend the duration of the session. In order for this to work, you must register the middleware: ```swift -var middlewares = MiddlewareConfig() -// ... -middlewares.use(BlogRememberMeMiddleware.self) -middlewares.use(SessionsMiddleware.self) -services.register(middlewares) +application.middlewares.use(BlogRememberMeMiddleware()) ``` **Note:** This must be registered before you register the `SessionsMiddleware`. -SteamPress uses a `PasswordVerifier` protocol to check passwords. Vapor doesn't provide a default BCrypt implementation for this, so you must register this yourself: - -```swift -config.prefer(BCryptDigest.self, for: PasswordVerifier.self) -``` +**TODO: Update** Finally, if you wish to use the `#markdown()` tag with your blog Leaf templates, you must register this. There's also a paginator tag, to make pagination easy: ```swift - var tags = LeafTagConfig.default() +var tags = LeafTagConfig.default() tags.use(Markdown(), as: "markdown") let paginatorTag = PaginatorTag(paginationLabel: "Blog Posts") tags.use(paginatorTag, as: PaginatorTag.name) @@ -175,7 +164,7 @@ let feedInformation = FeedInformation( description: "SteamPress is an open-source blogging engine written for Vapor in Swift", copyright: "Released under the MIT licence", imageURL: "https://user-images.githubusercontent.com/9938337/29742058-ed41dcc0-8a6f-11e7-9cfc-680501cdfb97.png") -try services.register(SteamPressFluentPostgresProvider(blogPath: "blog", feedInformation: feedInformation, postsPerPage: 5)) +application.steampress.configuration = SteamPressConfiguration(blogPath: "blog", feedInformation: feedInformation, postsPerPage: 5) ``` Additionally, you should set the `WEBSITE_URL` environment variable to the root address of your website, e.g. `https://www.steampress.io`. This is used to set various parameters throughout SteamPress. From f79c6778cfa465a1748c2b7baa8811c26bfd9170 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 17 Apr 2020 09:16:55 +0100 Subject: [PATCH 66/70] Add new initialiser to help repositories --- Sources/SteamPress/Models/BlogPost.swift | 12 ++++++++++++ Tests/SteamPressTests/BlogTests/PostTests.swift | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/Sources/SteamPress/Models/BlogPost.swift b/Sources/SteamPress/Models/BlogPost.swift index 63035a4b..ca9cf3d5 100644 --- a/Sources/SteamPress/Models/BlogPost.swift +++ b/Sources/SteamPress/Models/BlogPost.swift @@ -29,6 +29,18 @@ public final class BlogPost: Codable { self.lastEdited = nil self.published = published } + + public init(blogID: Int? = nil, title: String, contents: String, authorID: Int, creationDate: Date, slugUrl: String, + published: Bool) { + self.blogID = blogID + self.title = title + self.contents = contents + self.author = authorID + self.created = creationDate + self.slugUrl = slugUrl + self.lastEdited = nil + self.published = published + } } // MARK: - BlogPost Utilities diff --git a/Tests/SteamPressTests/BlogTests/PostTests.swift b/Tests/SteamPressTests/BlogTests/PostTests.swift index b9874c20..0fda83a9 100644 --- a/Tests/SteamPressTests/BlogTests/PostTests.swift +++ b/Tests/SteamPressTests/BlogTests/PostTests.swift @@ -77,4 +77,9 @@ class PostTests: XCTestCase { XCTAssertEqual(tags.first?.name, tag1Name) XCTAssertEqual(tags.last?.name, tag2Name) } + + func testExtraInitialiserWorks() throws { + let post = BlogPost(blogID: 1, title: "title", contents: "contents", authorID: 1, creationDate: Date(), slugUrl: "slug-url", published: true) + XCTAssertEqual(post.blogID, 1) + } } From 035bbaed843c3fe1274fa976bf5a799fb5bdb7b8 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 17 Apr 2020 15:57:13 +0100 Subject: [PATCH 67/70] Make repositories public on application in case anyone needs to access them --- .../Repositories/Application+SteamPress+Repositories.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/SteamPress/Repositories/Application+SteamPress+Repositories.swift b/Sources/SteamPress/Repositories/Application+SteamPress+Repositories.swift index 2ed49dee..8ff08bf6 100644 --- a/Sources/SteamPress/Repositories/Application+SteamPress+Repositories.swift +++ b/Sources/SteamPress/Repositories/Application+SteamPress+Repositories.swift @@ -23,21 +23,21 @@ public extension Application.SteamPress { let application: Application - var userRepository: BlogUserRepository { + public var userRepository: BlogUserRepository { guard let makeRepository = self.storage.makeUserRepository else { fatalError("No user repository configured. Configure with app.blogRepositories.use(...)") } return makeRepository(self.application) } - var postRepository: BlogPostRepository { + public var postRepository: BlogPostRepository { guard let makeRepository = self.storage.makePostRepository else { fatalError("No post repository configured. Configure with app.blogRepositories.use(...)") } return makeRepository(self.application) } - var tagRepository: BlogTagRepository { + public var tagRepository: BlogTagRepository { guard let makeRepository = self.storage.makeTagRepository else { fatalError("No tag repository configured. Configure with app.blogRepositories.use(...)") } From ee9d336dd3c88dae4d6827bd3c13071696249eb6 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 17 Apr 2020 15:58:32 +0100 Subject: [PATCH 68/70] Expose reset password required in the user initialiser --- Sources/SteamPress/Models/BlogUser.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/SteamPress/Models/BlogUser.swift b/Sources/SteamPress/Models/BlogUser.swift index 69c431f1..cb11c9f9 100644 --- a/Sources/SteamPress/Models/BlogUser.swift +++ b/Sources/SteamPress/Models/BlogUser.swift @@ -8,17 +8,18 @@ public final class BlogUser: Codable { public var name: String public var username: String public var password: String - public var resetPasswordRequired: Bool = false + public var resetPasswordRequired: Bool public var profilePicture: String? public var twitterHandle: String? public var biography: String? public var tagline: String? - public init(userID: Int? = nil, name: String, username: String, password: String, profilePicture: String?, twitterHandle: String?, biography: String?, tagline: String?) { + public init(userID: Int? = nil, name: String, username: String, password: String, resetPasswordRequired: Bool = false, profilePicture: String?, twitterHandle: String?, biography: String?, tagline: String?) { self.userID = userID self.name = name self.username = username.lowercased() self.password = password + self.resetPasswordRequired = resetPasswordRequired self.profilePicture = profilePicture self.twitterHandle = twitterHandle self.biography = biography From 514232a3dd3239851f76c567ca871735701f5a75 Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 17 Apr 2020 16:37:14 +0100 Subject: [PATCH 69/70] Add new initialiser for adding individual repositories --- .../Application+SteamPress+Repositories.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sources/SteamPress/Repositories/Application+SteamPress+Repositories.swift b/Sources/SteamPress/Repositories/Application+SteamPress+Repositories.swift index 8ff08bf6..907b4260 100644 --- a/Sources/SteamPress/Repositories/Application+SteamPress+Repositories.swift +++ b/Sources/SteamPress/Repositories/Application+SteamPress+Repositories.swift @@ -54,6 +54,18 @@ public extension Application.SteamPress { self.storage.makePostRepository = makeRespository } + public func use(_ makeRepository: @escaping (Application) -> BlogUserRepository) { + self.storage.makeUserRepository = makeRepository + } + + public func use(_ makeRepository: @escaping (Application) -> BlogTagRepository) { + self.storage.makeTagRepository = makeRepository + } + + public func use(_ makeRepository: @escaping (Application) -> BlogPostRepository) { + self.storage.makePostRepository = makeRepository + } + public func initialize() { self.application.storage[Key.self] = .init() } From 376f57fe71b7a9014c6701ff8a6e5a5d0ef423bc Mon Sep 17 00:00:00 2001 From: Tim <0xtimc@gmail.com> Date: Fri, 17 Apr 2020 16:47:49 +0100 Subject: [PATCH 70/70] Make applicaiton in steampress extension public to make it easy to build on --- Sources/SteamPress/SteamPress+Application.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SteamPress/SteamPress+Application.swift b/Sources/SteamPress/SteamPress+Application.swift index 2cae2871..2a09e626 100644 --- a/Sources/SteamPress/SteamPress+Application.swift +++ b/Sources/SteamPress/SteamPress+Application.swift @@ -2,7 +2,7 @@ import Vapor extension Application { public class SteamPress { - let application: Application + public let application: Application let lifecycleHandler: SteamPressRoutesLifecycleHandler init(application: Application, lifecycleHandler: SteamPressRoutesLifecycleHandler) {