Skip to content

Commit

Permalink
Merge pull request #1 from brokenhandsio/release
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
0xTim authored Mar 26, 2020
2 parents b2b0377 + 8da46d7 commit b5526f0
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
coverage:
range: "0...100"
ignore:
- "Tests/"
- ".build/"
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ let package = Package(
],
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/brokenhandsio/SteamPress.git", from: "1.0.0-alpha"),
.package(url: "https://github.com/brokenhandsio/SteamPress.git", from: "1.0.0"),
.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "3.1.0")
],
targets: [
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</a>
</p>

Steampress Fluent Postgres provides Fluent PostgreSQL adapters for SteamPress to allow you to use SteamPress with a PostgreSQL database.
Steampress Fluent MySQL provides Fluent MySQL adapters for SteamPress to allow you to use SteamPress with a MySQL database.

# Usage:

Expand All @@ -25,7 +25,7 @@ Add the package to your **Package.swift** dependencies:
```swift
dependencies: [
...,
.package(url: "https://github.com/brokenhandsio/steampress-fluent-mysql.git", from: "0.1.0"),
.package(name: "SteampressFluentMysql", url: "https://github.com/brokenhandsio/steampress-fluent-mysql.git", from: "1.0.0"),
]
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@ import FluentMySQL
import SteamPress

struct FluentMysqlPostRepository: BlogPostRepository, Service {
func getAllPostsCount(includeDrafts: Bool, on container: Container) -> EventLoopFuture<Int> {
container.withPooledConnection(to: .mysql) { connection in
let query = BlogPost.query(on: connection)
if !includeDrafts {
query.filter(\.published == true)
}
return query.count()
}
}

func getPublishedPostCount(for tag: BlogTag, on container: Container) -> EventLoopFuture<Int> {
container.withPooledConnection(to: .mysql) { connection in
return try tag.posts.query(on: connection).filter(\.published == true).count()
}
}

func getPublishedPostCount(for searchTerm: String, on container: Container) -> EventLoopFuture<Int> {
container.withPooledConnection(to: .mysql) { connection in
BlogPost.query(on: connection).filter(\.published == true).group(.or) { or in
or.filter(\.title, .like, "%\(searchTerm)%")
or.filter(\.contents, .like, "%\(searchTerm)%")
}.count()
}
}


func getAllPostsSortedByPublishDate(includeDrafts: Bool, on container: Container) -> EventLoopFuture<[BlogPost]> {
container.requestPooledConnection(to: .mysql).flatMap { connection in
Expand Down Expand Up @@ -61,9 +86,14 @@ struct FluentMysqlPostRepository: BlogPostRepository, Service {
}
}

func findPublishedPostsOrdered(for searchTerm: String, on container: Container) -> EventLoopFuture<[BlogPost]> {
func findPublishedPostsOrdered(for searchTerm: String, on container: Container, count: Int, offset: Int) -> EventLoopFuture<[BlogPost]> {
container.requestPooledConnection(to: .mysql).flatMap { connection in
BlogPost.query(on: connection).sort(\.created, .descending).filter(\.published == true).group(.or) { or in
let query = BlogPost.query(on: connection).sort(\.created, .descending).filter(\.published == true)

let upperLimit = count + offset
let paginatedQuery = query.range(offset..<upperLimit)

return paginatedQuery.group(.or) { or in
or.filter(\.title, .like, "%\(searchTerm)%")
or.filter(\.contents, .like, "%\(searchTerm)%")
}.all()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ struct FluentMysqlTagRepository: BlogTagRepository, Service {
}
}

func getTagsForAllPosts(on container: Container) -> EventLoopFuture<[Int : [BlogTag]]> {
container.withPooledConnection(to: .mysql) { connection in
let allTagsQuery = BlogTag.query(on: connection).all()
let allPivotsQuery = BlogPostTagPivot.query(on: connection).all()
return map(allTagsQuery, allPivotsQuery) { tags, pivots in
let pivotsSortedByPost = Dictionary(grouping: pivots) { (pivot) -> Int in
return pivot.postID
}

let postsWithTags = pivotsSortedByPost.mapValues { value in
return value.map { pivot in
tags.first { $0.tagID == pivot.tagID }
}
}.mapValues { $0.compactMap { $0 } }

return postsWithTags
}
}
}

func getAllTagsWithPostCount(on container: Container) -> EventLoopFuture<[(BlogTag, Int)]> {
container.requestPooledConnection(to: .mysql).flatMap { connection in
let allTagsQuery = BlogTag.query(on: connection).all()
Expand Down Expand Up @@ -46,29 +66,35 @@ struct FluentMysqlTagRepository: BlogTagRepository, Service {
try post.tags.query(on: connection).all().flatMap { tags in
let tagIDs = tags.compactMap { $0.tagID }
return try BlogPostTagPivot.query(on: connection).filter(\.postID == post.requireID()).filter(\.tagID ~~ tagIDs).delete().flatMap { _ in
var tagCleanups = [EventLoopFuture<Void>]()
for tag in tags {
let tagCleanup = try tag.posts.query(on: connection).all().flatMap(to: Void.self) { posts in
let cleanupFuture: EventLoopFuture<Void>
if posts.count == 0 {
cleanupFuture = tag.delete(on: connection)
} else {
cleanupFuture = container.future()
}
return cleanupFuture
}
tagCleanups.append(tagCleanup)
}
return tagCleanups.flatten(on: container)
try self.cleanupTags(on: connection, tags: tags)
}
}
}
}

func remove(_ tag: BlogTag, from post: BlogPost, on container: Container) -> EventLoopFuture<Void> {
container.requestPooledConnection(to: .mysql).flatMap { connection in
post.tags.detach(tag, on: connection)
post.tags.detach(tag, on: connection).flatMap {
try self.cleanupTags(on: connection, tags: [tag])
}
}
}

func cleanupTags(on connection: MySQLConnection, tags: [BlogTag]) throws -> EventLoopFuture<Void> {
var tagCleanups = [EventLoopFuture<Void>]()
for tag in tags {
let tagCleanup = try tag.posts.query(on: connection).all().flatMap(to: Void.self) { posts in
let cleanupFuture: EventLoopFuture<Void>
if posts.count == 0 {
cleanupFuture = tag.delete(on: connection)
} else {
cleanupFuture = connection.future()
}
return cleanupFuture
}
tagCleanups.append(tagCleanup)
}
return tagCleanups.flatten(on: connection)
}

func add(_ tag: BlogTag, to post: BlogPost, on container: Container) -> EventLoopFuture<Void> {
Expand Down
37 changes: 36 additions & 1 deletion Tests/SteampressFluentMysqlTests/PostRepositoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,20 @@ class PostRepositoryTests: XCTestCase {
XCTAssertEqual(allPosts.last?.slugUrl, post2.slugUrl)
}

func testGetAllPostsCount() throws {
_ = try BlogPost(title: "A new post", contents: "Some Contents", author: postAuthor, creationDate: Date().addingTimeInterval(360), slugUrl: "a-new-post", published: true).save(on: connection).wait()
_ = try BlogPost(title: "A different post", contents: "Some other contents", author: postAuthor, creationDate: Date().addingTimeInterval(-360), slugUrl: "a-different-post", published: true).save(on: connection).wait()
_ = try BlogPost(title: "A third post", contents: "Some other contents", author: postAuthor, creationDate: Date(), slugUrl: "a-third-post", published: true).save(on: connection).wait()
_ = try BlogPost(title: "A draft post", contents: "Some other contents", author: postAuthor, creationDate: Date().addingTimeInterval(60), slugUrl: "a-draft-post", published: false).save(on: connection).wait()

let publishedPostsCount = try repository.getAllPostsCount(includeDrafts: false, on: app).wait()
XCTAssertEqual(publishedPostsCount, 3)

let allPostsCount = try repository.getAllPostsCount(includeDrafts: true, on: app).wait()

XCTAssertEqual(allPostsCount, 4)
}

func testSearchReturnsPublishedPostsInDateOrder() throws {
let post1 = try BlogPost(title: "A new post", contents: "Some Contents about vapor", author: postAuthor, creationDate: Date().addingTimeInterval(-360), slugUrl: "a-new-post", published: true).save(on: connection).wait()
let post2 = try BlogPost(title: "A different Vapor post", contents: "Some other contents", author: postAuthor, creationDate: Date().addingTimeInterval(360), slugUrl: "a-different-post", published: true).save(on: connection).wait()
Expand All @@ -128,11 +142,13 @@ class PostRepositoryTests: XCTestCase {
_ = try BlogPost(title: "An unrelated draft post", contents: "Some other contents", author: postAuthor, creationDate: Date(), slugUrl: "an-unrelated-draft-post", published: false).save(on: connection).wait()
_ = try BlogPost(title: "An unrelated post", contents: "Some other contents", author: postAuthor, creationDate: Date(), slugUrl: "an-unrelated-post", published: true).save(on: connection).wait()

let posts = try repository.findPublishedPostsOrdered(for: "vapor", on: app).wait()
let posts = try repository.findPublishedPostsOrdered(for: "vapor", on: app, count: 10, offset: 0).wait()
let count = try repository.getPublishedPostCount(for: "vapor", on: app).wait()

XCTAssertEqual(posts.count, 2)
XCTAssertEqual(posts.first?.slugUrl, post2.slugUrl)
XCTAssertEqual(posts.last?.slugUrl, post1.slugUrl)
XCTAssertEqual(count, 2)
}

func testGettingAllPostsForUser() throws {
Expand Down Expand Up @@ -228,5 +244,24 @@ class PostRepositoryTests: XCTestCase {
XCTAssertEqual(otherUserPosts.count, 1)
XCTAssertEqual(otherUserPosts.first?.slugUrl, post6.slugUrl)
}

func testGettingPostCountForATag() throws {
let tag = try BlogTag(name: "Engineering").save(on: connection).wait()

let post1 = try BlogPost(title: "A new post", contents: "Some Contents about vapor", author: postAuthor, creationDate: Date().addingTimeInterval(-360), slugUrl: "a-new-post", published: true).save(on: connection).wait()
let post2 = try BlogPost(title: "A different Vapor post", contents: "Some other contents", author: postAuthor, creationDate: Date().addingTimeInterval(360), slugUrl: "a-different-post", published: true).save(on: connection).wait()
let post3 = try BlogPost(title: "A third post", contents: "Some other contents containing vapor", author: postAuthor, creationDate: Date(), slugUrl: "a-third-post", published: true).save(on: connection).wait()
let post4 = try BlogPost(title: "A draft Vapor post", contents: "Some other contents", author: postAuthor, creationDate: Date().addingTimeInterval(60), slugUrl: "a-draft-post", published: true).save(on: connection).wait()
let post5 = try BlogPost(title: "An unrelated draft post", contents: "Some other contents", author: postAuthor, creationDate: Date().addingTimeInterval(10), slugUrl: "an-unrelated-draft-post", published: false).save(on: connection).wait()
_ = try post1.tags.attach(tag, on: connection).wait()
_ = try post2.tags.attach(tag, on: connection).wait()
_ = try post3.tags.attach(tag, on: connection).wait()
_ = try post4.tags.attach(tag, on: connection).wait()
_ = try post5.tags.attach(tag, on: connection).wait()

let count = try repository.getPublishedPostCount(for: tag, on: app).wait()

XCTAssertEqual(count, 4)
}
}

54 changes: 51 additions & 3 deletions Tests/SteampressFluentMysqlTests/TagRepositoryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class TagRepositoryTests: XCTestCase {
// MARK: - Tests

func testSavingTag() throws {
let newTag = try BlogTag(name: "SteamPress")
let newTag = BlogTag(name: "SteamPress")
let savedTag = try repository.save(newTag, on: app).wait()

XCTAssertNotNil(savedTag.tagID)
Expand All @@ -32,7 +32,7 @@ class TagRepositoryTests: XCTestCase {

func testGetingATag() throws {
let tagName = "Engineering"
let tag = try BlogTag(name: tagName)
let tag = BlogTag(name: tagName)
_ = try tag.save(on: connection).wait()

let retrievedTag = try repository.getTag(tagName, on: app).wait()
Expand Down Expand Up @@ -82,14 +82,40 @@ class TagRepositoryTests: XCTestCase {

func testRemovingTagFromPost() throws {
let tag = try BlogTag(name: "SteamPress").save(on: connection).wait()
let tag2 = try BlogTag(name: "Testing").save(on: connection).wait()
let user = try BlogUser(name: "Alice", username: "alice", password: "password", profilePicture: nil, twitterHandle: nil, biography: nil, tagline: nil).save(on: connection).wait()
let post = try BlogPost(title: "A Post", contents: "Some contents", author: user, creationDate: Date(), slugUrl: "a-post", published: true).save(on: connection).wait()
_ = try post.tags.attach(tag, on: connection).wait()
_ = try post.tags.attach(tag2, on: connection).wait()

try repository.remove(tag, from: post, on: app).wait()

let tagLinks = try BlogPostTagPivot.query(on: connection).all().wait()
XCTAssertEqual(tagLinks.count, 0)
XCTAssertEqual(tagLinks.count, 1)

let allTags = try BlogTag.query(on: connection).all().wait()
XCTAssertEqual(allTags.count, 1)
XCTAssertEqual(allTags.first?.name, tag2.name)
}

func testDeletingTagsForPostDoesntDeleteTagIfItsAttachedToAnotherPost() throws {
let tag1 = try BlogTag(name: "SteamPress").save(on: connection).wait()
let tag2 = try BlogTag(name: "Engineering").save(on: connection).wait()
let user = try BlogUser(name: "Alice", username: "alice", password: "password", profilePicture: nil, twitterHandle: nil, biography: nil, tagline: nil).save(on: connection).wait()
let post = try BlogPost(title: "A Post", contents: "Some contents", author: user, creationDate: Date(), slugUrl: "a-post", published: true).save(on: connection).wait()
let post2 = try BlogPost(title: "Another Post", contents: "Some other contents", author: user, creationDate: Date(), slugUrl: "another-post", published: true).save(on: connection).wait()

_ = try post.tags.attach(tag1, on: connection).wait()
_ = try post.tags.attach(tag2, on: connection).wait()
_ = try post2.tags.attach(tag2, on: connection).wait()

try repository.deleteTags(for: post, on: app).wait()

let tagCount = try BlogTag.query(on: connection).count().wait()
XCTAssertEqual(tagCount, 1)

let pivotCount = try BlogPostTagPivot.query(on: connection).count().wait()
XCTAssertEqual(pivotCount, 1)
}

func testGettingTagsForPost() throws {
Expand Down Expand Up @@ -145,4 +171,26 @@ class TagRepositoryTests: XCTestCase {
XCTAssertEqual(tagsWithPostCount.last?.0.name, tag1.name)
XCTAssertEqual(tagsWithPostCount.last?.1, 2)
}

func testGettingAllTagsWithPostID() throws {
let tag1 = try BlogTag(name: "SteamPress").save(on: connection).wait()
let tag2 = try BlogTag(name: "Engineering").save(on: connection).wait()
let user = try BlogUser(name: "Alice", username: "alice", password: "password", profilePicture: nil, twitterHandle: nil, biography: nil, tagline: nil).save(on: connection).wait()
let post1 = try BlogPost(title: "A Post", contents: "Some contents", author: user, creationDate: Date(), slugUrl: "a-post", published: true).save(on: connection).wait()
let post2 = try BlogPost(title: "A Second Post", contents: "Some contents", author: user, creationDate: Date(), slugUrl: "a-second-post", published: true).save(on: connection).wait()
let post3 = try BlogPost(title: "A Third Post", contents: "Some contents", author: user, creationDate: Date(), slugUrl: "a-third-post", published: true).save(on: connection).wait()

_ = try post1.tags.attach(tag1, on: connection).wait()
_ = try post2.tags.attach(tag2, on: connection).wait()
_ = try post3.tags.attach(tag1, on: connection).wait()

let tagsWithPosts = try repository.getTagsForAllPosts(on: app).wait()

XCTAssertEqual(tagsWithPosts[post1.blogID!]?.count, 1)
XCTAssertEqual(tagsWithPosts[post1.blogID!]?.first?.name, tag1.name)
XCTAssertEqual(tagsWithPosts[post2.blogID!]?.count, 1)
XCTAssertEqual(tagsWithPosts[post2.blogID!]?.first?.name, tag2.name)
XCTAssertEqual(tagsWithPosts[post3.blogID!]?.count, 1)
XCTAssertEqual(tagsWithPosts[post3.blogID!]?.first?.name, tag1.name)
}
}

0 comments on commit b5526f0

Please sign in to comment.