-
-
Notifications
You must be signed in to change notification settings - Fork 50
Custom package collections #3429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
f5b81b8
Add CustomCollection model + migration
finestructure c54a908
Add CustomCollectionPackage pivot
finestructure daf893b
Add test_CustomCollectionPackage_detach
finestructure bc5e3ef
Add test_CustomCollection_packages
finestructure 884a7ec
Add delete cascade tests
finestructure 29b0134
Add Package filter by urls
finestructure e6eb21c
Add PackageListRepositoryClient
finestructure eacd721
Add CustomCollection.reconcile
finestructure 181e9de
Add CustomCollection.findOrCreate
finestructure 9695b93
Add reconcileCustomCollection + test
finestructure 3f4f9ed
Add test_CustomCollection_reconcile_caseSensitive
finestructure ea6b60f
Wire up custom collection reconciliation (TODO: add final test, fix t…
finestructure a926920
Add DependencyKey conformance and liveValue impl
finestructure 8a20965
Fix tests
finestructure 1572f1f
Implement test_reconcile
finestructure efa1ede
Move custom package collection size limit to Constants
finestructure 14cbf3b
Add TODO
finestructure 2878b97
Add more logging
finestructure 5efe3f3
Fix custom collections url, improve error logging
finestructure File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
Sources/App/Core/Dependencies/PackageListRepositoryClient.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Copyright Dave Verwer, Sven A. Schmidt, and other contributors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import Dependencies | ||
import DependenciesMacros | ||
import Vapor | ||
|
||
|
||
@DependencyClient | ||
struct PackageListRepositoryClient { | ||
var fetchCustomCollection: @Sendable (_ client: Client, _ url: URL) async throws -> [URL] | ||
var fetchCustomCollections: @Sendable (_ client: Client) async throws -> [CustomCollection.DTO] | ||
// TODO: move other package list dependencies here | ||
} | ||
|
||
|
||
extension PackageListRepositoryClient: DependencyKey { | ||
static var liveValue: PackageListRepositoryClient { | ||
.init( | ||
fetchCustomCollection: { client, url in | ||
try await client | ||
.get(URI(string: url.absoluteString)) | ||
.content | ||
.decode([URL].self, using: JSONDecoder()) | ||
}, | ||
fetchCustomCollections: { client in | ||
try await client | ||
.get(Constants.customCollectionsUri) | ||
.content | ||
.decode([CustomCollection.DTO].self, using: JSONDecoder()) | ||
} | ||
) | ||
} | ||
} | ||
|
||
|
||
extension PackageListRepositoryClient: Sendable, TestDependencyKey { | ||
static var testValue: Self { Self() } | ||
} | ||
|
||
|
||
extension DependencyValues { | ||
var packageListRepository: PackageListRepositoryClient { | ||
get { self[PackageListRepositoryClient.self] } | ||
set { self[PackageListRepositoryClient.self] = newValue } | ||
} | ||
} | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// Copyright Dave Verwer, Sven A. Schmidt, and other contributors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import Fluent | ||
import SQLKit | ||
|
||
|
||
struct CreateCustomCollection: AsyncMigration { | ||
func prepare(on database: Database) async throws { | ||
try await database.schema("custom_collections") | ||
|
||
// managed fields | ||
.id() | ||
.field("created_at", .datetime) | ||
.field("updated_at", .datetime) | ||
|
||
// data fields | ||
.field("name", .string, .required) | ||
.field("description", .string) | ||
.field("badge", .string) | ||
.field("url", .string, .required) | ||
|
||
// constraints | ||
.unique(on: "name") | ||
.unique(on: "url") | ||
|
||
.create() | ||
} | ||
|
||
func revert(on database: Database) async throws { | ||
try await database.schema("custom_collections").delete() | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
Sources/App/Migrations/081/CreateCustomCollectionPackage.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Copyright Dave Verwer, Sven A. Schmidt, and other contributors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import Fluent | ||
import SQLKit | ||
|
||
|
||
struct CreateCustomCollectionPackage: AsyncMigration { | ||
func prepare(on database: Database) async throws { | ||
try await database.schema("custom_collections+packages") | ||
|
||
// managed fields | ||
.id() | ||
.field("created_at", .datetime) | ||
.field("updated_at", .datetime) | ||
|
||
// reference fields | ||
.field("custom_collection_id", .uuid, | ||
.references("custom_collections", "id", onDelete: .cascade), .required) | ||
.field("package_id", .uuid, | ||
.references("packages", "id", onDelete: .cascade), .required) | ||
|
||
// constraints | ||
.unique(on: "custom_collection_id", "package_id") | ||
|
||
.create() | ||
} | ||
|
||
func revert(on database: Database) async throws { | ||
try await database.schema("custom_collections+packages").delete() | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Copyright Dave Verwer, Sven A. Schmidt, and other contributors. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
import Fluent | ||
import Vapor | ||
|
||
|
||
final class CustomCollection: @unchecked Sendable, Model, Content { | ||
static let schema = "custom_collections" | ||
|
||
typealias Id = UUID | ||
|
||
// managed fields | ||
|
||
@ID(key: .id) | ||
var id: Id? | ||
|
||
@Timestamp(key: "created_at", on: .create) | ||
var createdAt: Date? | ||
|
||
// periphery:ignore | ||
@Timestamp(key: "updated_at", on: .update) | ||
var updatedAt: Date? | ||
|
||
// data fields | ||
|
||
@Field(key: "name") | ||
var name: String | ||
|
||
@Field(key: "description") | ||
var description: String? | ||
|
||
@Field(key: "badge") | ||
var badge: String? | ||
|
||
@Field(key: "url") | ||
var url: URL | ||
|
||
// reference fields | ||
@Siblings(through: CustomCollectionPackage.self, from: \.$customCollection, to: \.$package) | ||
var packages: [Package] | ||
|
||
init() { } | ||
|
||
init(id: Id? = nil, createdAt: Date? = nil, updatedAt: Date? = nil, _ dto: DTO) { | ||
self.id = id | ||
self.createdAt = createdAt | ||
self.updatedAt = updatedAt | ||
self.name = dto.name | ||
self.description = dto.description | ||
self.badge = dto.badge | ||
self.url = dto.url | ||
} | ||
} | ||
|
||
|
||
extension CustomCollection { | ||
struct DTO: Codable { | ||
var name: String | ||
var description: String? | ||
var badge: String? | ||
var url: URL | ||
} | ||
|
||
static func findOrCreate(on database: Database, _ dto: DTO) async throws -> CustomCollection { | ||
if let collection = try await CustomCollection.query(on: database) | ||
.filter(\.$url == dto.url) | ||
.first() { | ||
return collection | ||
} else { | ||
let collection = CustomCollection(dto) | ||
try await collection.save(on: database) | ||
return collection | ||
} | ||
} | ||
|
||
func reconcile(on database: Database, packageURLs: some Collection<URL>) async throws { | ||
let incoming: [Package.Id: Package] = .init( | ||
packages: try await Package.query(on: database) | ||
.filter(by: packageURLs) | ||
.all() | ||
) | ||
try await $packages.load(on: database) | ||
let existing: [Package.Id: Package] = .init(packages: packages) | ||
let newIDs = Set(incoming.keys).subtracting(Set(existing.keys)) | ||
try await $packages.attach(incoming[newIDs], on: database) | ||
let removedIDs = Set(existing.keys).subtracting(Set(incoming.keys)) | ||
try await $packages.detach(existing[removedIDs], on: database) | ||
} | ||
} | ||
|
||
|
||
private extension [Package.Id: Package] { | ||
init(packages: [Package]) { | ||
self.init( | ||
packages.compactMap({ pkg in pkg.id.map({ ($0, pkg) }) }), | ||
uniquingKeysWith: { (first, second) in first } | ||
) | ||
} | ||
|
||
subscript(ids: some Collection<Package.Id>) -> [Package] { | ||
Array(ids.compactMap { self[$0] }) | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will do this in a follow-up PR right after this one but I wanted to keep the change set focused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, this illustrates how we would set up grouped dependencies (like Github, for instance) with the new
Dependencies
mechanism.