Skip to content

Commit

Permalink
๐Ÿ› ๏ธ Introduce structured logging
Browse files Browse the repository at this point in the history
๐Ÿ› ๏ธ Add BitArray data structure
๐Ÿ› ๏ธ Add BloomFilter data structure
๐Ÿ› ๏ธ Refactor image handling for items
๐Ÿ› ๏ธ Improve env file parsing
๐Ÿ› ๏ธ Add more tests
โ˜˜ Fix sending invitation mail
  • Loading branch information
edmw committed Feb 25, 2020
1 parent 8b9b8a3 commit acbb125
Show file tree
Hide file tree
Showing 116 changed files with 16,216 additions and 13,298 deletions.
3 changes: 2 additions & 1 deletion .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,5 @@ identifier_name:
- min
- max
- nil

- bit
- now
19 changes: 15 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,24 @@

## [..]
- ๐ŸŽ Add functionality to move items between wishlists

## [1.4.1]
- ๐Ÿ› ๏ธ Introduce code generation
- ๐Ÿ› ๏ธ Introduce structured logging
- ๐Ÿ› ๏ธ Add BitArray data structure
- ๐Ÿ› ๏ธ Add BloomFilter data structure
- ๐Ÿ› ๏ธ Add data objects for database repositories
- ๐Ÿ› ๏ธ Make ids for entities into value objects
- ๐Ÿ› ๏ธ Refactor image handling for items
- ๐Ÿ› ๏ธ Improve env file parsing
- ๐Ÿ› ๏ธ Add tests
- โ˜˜ Fix sending invitation mail

## [1.4.0]
- ๐ŸŽ Add invitation emails
- โ˜˜ fix link to fav icon
- โ˜˜ fix deletion of reservation for list owner
- ๐Ÿ› ๏ธ Major refactoring to implement separation of concerns
- โ˜˜ Fix link to fav icon
- โ˜˜ Fix deletion of reservation for list owner

```
CLOC before refactoring:
Expand All @@ -32,14 +43,14 @@ Swift 383 4390 2500 16025
```

## [1.3.3]
- โ˜˜ fix link to about page
- ๐Ÿ› ๏ธ Add favicon
- โ˜˜ Fix link to about page

## [1.3.2]
- ๐ŸŽ Add pages for legal notice and privacy policy

## [1.3.1]
- โ˜˜ fix option "Don't spoil my surprises" on lists for notifications
- โ˜˜ Fix option "Don't spoil my surprises" on lists for notifications

## [1.3.0]
- ๐ŸŽ Add settings for user in profile
Expand Down
8 changes: 4 additions & 4 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,8 @@
"repositoryURL": "https://github.com/vapor/url-encoded-form.git",
"state": {
"branch": null,
"revision": "82d8d63bdb76b6dd8febe916c639ab8608dbbaed",
"version": "1.0.6"
"revision": "44999b47d88ca2e0c0b893599b3240e342ba39ff",
"version": "1.0.7"
}
},
{
Expand All @@ -285,8 +285,8 @@
"repositoryURL": "https://github.com/vapor/vapor.git",
"state": {
"branch": null,
"revision": "82ca8809b120985ebba756cc0f5d47dd5cbf6ed1",
"version": "3.3.2"
"revision": "642f3d4d1f0eafad651c85524d0d1c698b55399f",
"version": "3.3.3"
}
},
{
Expand Down
8 changes: 8 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,31 @@ let package = Package(
"App"
]
),
.target(
name: "Testing",
dependencies: [],
path: "Tests/Testing"
),
.testTarget(
name: "LibraryTests",
dependencies: [
"Testing",
"Library"
],
path: "Tests/LibraryTests"
),
.testTarget(
name: "DomainTests",
dependencies: [
"Testing",
"Domain"
],
path: "Tests/DomainTests"
),
.testTarget(
name: "AppTests",
dependencies: [
"Testing",
"App",
"VaporTestTools",
"LoggerAPI"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public struct FluentItem: ItemModel,
public var imageURL: URL?
public var createdAt: Date
public var modifiedAt: Date
public var localImageURL: URL?
public var localImageURL: ImageStoreLocator?
public var listKey: UUID
public var listID: ListID { ListID(uuid: listKey) }

Expand All @@ -54,7 +54,7 @@ public struct FluentItem: ItemModel,
imageURL: URL?,
createdAt: Date,
modifiedAt: Date,
localImageURL: URL?,
localImageURL: ImageStoreLocator?,
listKey: UUID
) {
self.uuid = uuid
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ struct VaporEmailSendingProvider: EmailSendingProvider {
self.request = request
}

// func sendInvitationEmail -> implemented in file `Scenes/Invitations/InvitationEmail`
// func dispatchSendInvitationEmail -> implemented in file `Scenes/Invitations/InvitationEmail`

}
119 changes: 97 additions & 22 deletions Sources/App/Domain/Adapters/Driven/VaporImageStoreProvider.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Domain
import Library

import Vapor

Expand All @@ -15,54 +16,128 @@ struct VaporImageStoreProvider: ImageStoreProvider {
self.request = request
}

func storeImage(for imagable: Imageable, from url: URL) throws -> EventLoopFuture<URL?> {
return try request.make(ImageFileMiddleware.self)
.upload(
func storeImage(for imagable: Imageable, from url: URL)
throws -> EventLoopFuture<ImageStoreLocator?>
{
let imageFileMiddleware = try request.make(ImageFileMiddleware.self)
return try imageFileMiddleware
.uploadImage(
from: url,
width: imagable.imageableSize.width,
height: imagable.imageableSize.height,
key: requireKey(for: imagable),
groupkey: requireGroupKey(for: imagable),
groupkeys: requireGroupKeys(for: imagable),
on: request
)
.map { imagefilelocator in
guard let url = imagefilelocator?.url else {
return nil
}
return ImageStoreLocator(url)
}
}

func removeImages(for imagable: Imageable) throws {
try request.make(ImageFileMiddleware.self)
.removeAll(
key: requireKey(for: imagable),
groupkey: requireGroupKey(for: imagable),
deleteDirectory: true,
let imageFileMiddleware = try request.make(ImageFileMiddleware.self)
try imageFileMiddleware.removeImages(
key: requireKey(for: imagable),
groupkeys: requireGroupKeys(for: imagable),
deleteParentsIfEmpty: true,
on: request
)
}

func removeImage(at locator: ImageStoreLocator) throws {
let imageFileMiddleware = try request.make(ImageFileMiddleware.self)
if let imageFileLocator = imageFileMiddleware.imageFileLocator(from: locator.url) {
try imageFileMiddleware.removeFile(
at: imageFileLocator,
deleteParentsIfEmpty: true,
on: request
)
}
}

private static let validCharactersForImageableKey
= CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-"))

private func requireKey(for imagable: Imageable) throws -> String {
guard let key = imagable.imageableEntityKey else {
throw ImageableError.keyMissing(\Imageable.imageableEntityKey)
}
let validCharactersForImageableKey
= CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-"))
guard validCharactersForImageableKey.isSuperset(
guard VaporImageStoreProvider.validCharactersForImageableKey.isSuperset(
of: CharacterSet(charactersIn: key)
) else {
throw ImageableError.keyInvalid(\Imageable.imageableEntityKey)
}
return key
}

private func requireGroupKey(for imagable: Imageable) throws -> String {
guard let groupkey = imagable.imageableEntityGroupKey else {
throw ImageableError.keyMissing(\Imageable.imageableEntityGroupKey)
private static let validCharactersForImageableGroupKey
= CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-"))

private func requireGroupKeys(for imagable: Imageable) throws -> [String] {
guard let groupkeys = imagable.imageableEntityGroupKeys else {
throw ImageableError.keyMissing(\Imageable.imageableEntityGroupKeys)
}
let validCharactersForImageableGroupKey
= CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-"))
guard validCharactersForImageableGroupKey.isSuperset(
of: CharacterSet(charactersIn: groupkey)
) else {
throw ImageableError.keyInvalid(\Imageable.imageableEntityGroupKey)
for groupkey in groupkeys {
guard VaporImageStoreProvider.validCharactersForImageableGroupKey.isSuperset(
of: CharacterSet(charactersIn: groupkey)
) else {
throw ImageableError.keyInvalid(\Imageable.imageableEntityGroupKeys)
}
}
return groupkeys
}

// MARK: CleanupJob

final class CleanupJob: DispatchableJob<Bool> {

override func run(_ context: JobContext) -> EventLoopFuture<Bool> {
let container = context.container
do {
let itemRepository: ItemRepository = try container.make()
return itemRepository.all().map { items in
// collect local image urls from all items into a bloom filter
var bloomfilter = BloomFilter<String>(expectedNumberOfElements: items.count)
for item in items {
guard let localimageurl = item.localImageURL else {
continue
}
bloomfilter.insert(localimageurl.absoluteString)
}
// iterate local file urls for images
let now = Date()
let imageFileMiddleware: ImageFileMiddleware = try container.make()
for imagefilelocator in imageFileMiddleware.listFiles(createdBefore: now) {
// if a image file url is not contained in the bloom filter the image is
// definitely not used for an item, so it is save to delete
let imagestorelocator = ImageStoreLocator(imagefilelocator.url)
if bloomfilter.containsNot(imagestorelocator.absoluteString) {
try imageFileMiddleware.removeFile(
at: imagefilelocator,
deleteParentsIfEmpty: true,
on: container
)
}
}
imageFileMiddleware.purgeDirectories(on: container)
return true
}
}
catch {
container.logger?.error("ImagesCleanupJob failed with \(error)")
}
return container.future(false)
}
return groupkey

// MARK: CustomStringConvertible

override var description: String {
return "ImagesCleanupJob(at: \(scheduled))"
}

}

}
12 changes: 6 additions & 6 deletions Sources/App/Scenes/Invitations/InvitationEmail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ final class InvitationEmail: HTMLMessage, CustomStringConvertible {

extension VaporEmailSendingProvider {

func sendInvitationEmail(_ invitation: InvitationRepresentation, for user: UserRepresentation)
throws -> EventLoopFuture<Bool>
{
func dispatchSendInvitationEmail(
_ invitation: InvitationRepresentation,
for user: UserRepresentation
) throws -> EventLoopFuture<Bool> {
var email = InvitationEmail(for: invitation, invitedBy: user)
email.addEmailRecipient(EmailAddress(String(invitation.email)))
return try email.send(on: request)
.map { sendResult in sendResult.success == true }
email.addEmailRecipient(EmailAddress(invitation.email))
return try email.dispatchSend(on: request).transform(to: true)
}

}
8 changes: 2 additions & 6 deletions Sources/App/Site.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,9 @@ struct Site: Codable, CustomDebugStringConvertible, ServiceType {

// MARK: - Vapor Service

static var serviceSupports: [Any.Type] {
return [Site.self]
}
static let serviceSupports: [Any.Type] = [Site.self]

static func makeService(for container: Container) throws
-> Site
{
static func makeService(for container: Container) throws -> Site {
return try .detect()
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/App/[Framework]/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension Message {
{
let job = SendMessageJob(for: self, on: container, at: date, before: deadline)
let jobService = try container.make(DispatchingService.self)
return try jobService.dispatch(AnyJob(job))
return try jobService.dispatch(job)
.transform(to: job.completed)
}

Expand All @@ -42,7 +42,7 @@ extension Message {
{
let job = SendMessageJob(for: self, on: container, at: date, before: deadline)
let jobService = try container.make(DispatchingService.self)
return try jobService.dispatch(AnyJob(job))
return try jobService.dispatch(job)
}

}
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/[Library]/URL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ enum URLType {
/// An absolute URL contains all the information necessary to locate a resource without
/// a fragment identifier.
case webAbsolute
/// An absolute URL contains with en empty path:
/// An absolute URL with an empty path:
case webAbsolutePathEmpty
}

Expand Down
Loading

0 comments on commit acbb125

Please sign in to comment.