Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions FrontEnd/styles/package.scss
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,17 @@
align-items: center;
}
}

li.custom-collections {
grid-column-start: span 2;
background-image: var(--image-tags);

a {
display: flex;
gap: 5px;
align-items: center;
}
}
}

section.sidebar-links {
Expand Down
4 changes: 2 additions & 2 deletions Sources/App/Commands/Reconcile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ func processPackageDenyList(packageList: [URL], denyList: [URL]) -> [URL] {
}


func reconcileCustomCollection(client: Client, database: Database, fullPackageList: [URL], _ dto: CustomCollection.DTO) async throws {
let collection = try await CustomCollection.findOrCreate(on: database, dto)
func reconcileCustomCollection(client: Client, database: Database, fullPackageList: [URL], _ details: CustomCollection.Details) async throws {
let collection = try await CustomCollection.findOrCreate(on: database, details)

// Limit incoming URLs to 50 since this is input outside of our control
@Dependency(\.packageListRepository) var packageListRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ extension API.PackageController.GetRoute {
var fundingLinks: [FundingLink]
var swift6Readiness: Swift6Readiness?
var forkedFromInfo: ForkedFromInfo?
var customCollections: [CustomCollection.Details]

internal init(packageId: Package.Id,
repositoryOwner: String,
Expand Down Expand Up @@ -83,7 +84,8 @@ extension API.PackageController.GetRoute {
preReleaseReference: App.Reference?,
fundingLinks: [FundingLink] = [],
swift6Readiness: Swift6Readiness?,
forkedFromInfo: ForkedFromInfo?
forkedFromInfo: ForkedFromInfo?,
customCollections: [CustomCollection.Details]
) {
self.packageId = packageId
self.repositoryOwner = repositoryOwner
Expand Down Expand Up @@ -126,6 +128,7 @@ extension API.PackageController.GetRoute {
self.fundingLinks = fundingLinks
self.swift6Readiness = swift6Readiness
self.forkedFromInfo = forkedFromInfo
self.customCollections = customCollections
}

init?(result: API.PackageController.PackageResult,
Expand All @@ -136,7 +139,8 @@ extension API.PackageController.GetRoute {
platformBuildInfo: BuildInfo<CompatibilityMatrix.PlatformCompatibility>?,
weightedKeywords: [WeightedKeyword] = [],
swift6Readiness: Swift6Readiness?,
forkedFromInfo: ForkedFromInfo?) {
forkedFromInfo: ForkedFromInfo?,
customCollections: [CustomCollection.Details]) {
// we consider certain attributes as essential and return nil (raising .notFound)
let repository = result.repository
guard
Expand Down Expand Up @@ -182,7 +186,8 @@ extension API.PackageController.GetRoute {
preReleaseReference: result.preReleaseVersion?.reference,
fundingLinks: result.repository.fundingLinks,
swift6Readiness: swift6Readiness,
forkedFromInfo: forkedFromInfo
forkedFromInfo: forkedFromInfo,
customCollections: customCollections
)

}
Expand Down
14 changes: 13 additions & 1 deletion Sources/App/Controllers/API/API+PackageController+GetRoute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ extension API.PackageController {
repository: repository)
async let forkedFromInfo = forkedFromInfo(on: database, fork: packageResult.repository.forkedFrom)

async let customCollections = customCollections(on: database, package: packageResult.package)

guard
let model = try await Self.Model(
result: packageResult,
Expand All @@ -57,7 +59,8 @@ extension API.PackageController {
platformBuildInfo: buildInfo.platform,
weightedKeywords: weightedKeywords,
swift6Readiness: buildInfo.swift6Readiness,
forkedFromInfo: forkedFromInfo
forkedFromInfo: forkedFromInfo,
customCollections: customCollections
),
let schema = API.PackageSchema(result: packageResult)
else {
Expand Down Expand Up @@ -96,6 +99,15 @@ extension API.PackageController.GetRoute {
return .fromGitHub(url: url)
}
}

static func customCollections(on database: Database, package: Package) async -> [CustomCollection.Details] {
do {
try await package.$customCollections.load(on: database)
return package.customCollections.map(\.details)
} catch {
return []
}
}
}


Expand Down
3 changes: 2 additions & 1 deletion Sources/App/Controllers/API/Types+WithExample.swift
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ extension API.PackageController.GetRoute.Model: WithExample {
releaseReference: .tag(1, 2, 3, "1.2.3"),
preReleaseReference: nil,
swift6Readiness: nil,
forkedFromInfo: nil)
forkedFromInfo: nil,
customCollections: [])
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ struct PackageListRepositoryClient {
var fetchPackageList: @Sendable (_ client: Client) async throws -> [URL]
var fetchPackageDenyList: @Sendable (_ client: Client) async throws -> [URL]
var fetchCustomCollection: @Sendable (_ client: Client, _ url: URL) async throws -> [URL]
var fetchCustomCollections: @Sendable (_ client: Client) async throws -> [CustomCollection.DTO]
var fetchCustomCollections: @Sendable (_ client: Client) async throws -> [CustomCollection.Details]
}


Expand Down Expand Up @@ -62,7 +62,7 @@ extension PackageListRepositoryClient: DependencyKey {
try await client
.get(Constants.customCollectionsUri)
.content
.decode([CustomCollection.DTO].self, using: JSONDecoder())
.decode([CustomCollection.Details].self, using: JSONDecoder())
}
)
}
Expand Down
25 changes: 15 additions & 10 deletions Sources/App/Models/CustomCollection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,39 +47,40 @@ final class CustomCollection: @unchecked Sendable, Model, Content {
@Field(key: "url")
var url: URL

// reference fields
// relationships

@Siblings(through: CustomCollectionPackage.self, from: \.$customCollection, to: \.$package)
var packages: [Package]

init() { }

init(id: Id? = nil, createdAt: Date? = nil, updatedAt: Date? = nil, _ dto: DTO) {
init(id: Id? = nil, createdAt: Date? = nil, updatedAt: Date? = nil, _ details: Details) {
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
self.name = details.name
self.description = details.description
self.badge = details.badge
self.url = details.url
}
}


extension CustomCollection {
struct DTO: Codable {
struct Details: Codable, Equatable {
var name: String
var description: String?
var badge: String?
var url: URL
}

static func findOrCreate(on database: Database, _ dto: DTO) async throws -> CustomCollection {
static func findOrCreate(on database: Database, _ details: Details) async throws -> CustomCollection {
if let collection = try await CustomCollection.query(on: database)
.filter(\.$url == dto.url)
.filter(\.$url == details.url)
.first() {
return collection
} else {
let collection = CustomCollection(dto)
let collection = CustomCollection(details)
try await collection.save(on: database)
return collection
}
Expand All @@ -98,6 +99,10 @@ extension CustomCollection {
let removedIDs = Set(existing.keys).subtracting(Set(incoming.keys))
try await $packages.detach(existing[removedIDs], on: database)
}

var details: Details {
.init(name: name, description: description, badge: badge, url: url)
}
}


Expand Down
3 changes: 3 additions & 0 deletions Sources/App/Models/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ final class Package: @unchecked Sendable, Model, Content {

// relationships

@Siblings(through: CustomCollectionPackage.self, from: \.$package, to: \.$customCollection)
var customCollections: [CustomCollection]

@Children(for: \.$package)
var repositories: [Repository]

Expand Down
13 changes: 13 additions & 0 deletions Sources/App/Views/PackageController/GetRoute.Model+ext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,19 @@ extension API.PackageController.GetRoute.Model {
}
}

func customCollectionsItem() -> Node<HTML.ListContext> {
guard !customCollections.isEmpty else { return .empty }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's gate this to staging for now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

return .li(
.class("custom-collections"),
.forEach(customCollections, { collection in
.a(
.href(collection.url), // FIXME: link to custom collection page
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be a follow-up PR

.text("\(collection.name)")
)
})
)
}

func latestReleaseMetadata() -> Node<HTML.ListContext> {
guard let dateLink = releases.stable else { return .empty }
return releaseMetadata(dateLink, title: "Latest Release", cssClass: "stable")
Expand Down
3 changes: 2 additions & 1 deletion Sources/App/Views/PackageController/PackageShow+View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ extension PackageShow {
model.productTypeListItem(.plugin),
model.targetTypeListItem(.macro),
model.dataRaceSafeListItem(),
model.keywordsListItem()
model.keywordsListItem(),
model.customCollectionsItem()
)
)
}
Expand Down
12 changes: 8 additions & 4 deletions Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase {
platformBuildInfo: nil,
weightedKeywords: [],
swift6Readiness: nil,
forkedFromInfo: nil)
forkedFromInfo: nil,
customCollections: [])

// validate
XCTAssertNotNil(m)
Expand All @@ -66,7 +67,8 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase {
platformBuildInfo: nil,
weightedKeywords: [],
swift6Readiness: nil,
forkedFromInfo: nil))
forkedFromInfo: nil,
customCollections: []))

// validate
XCTAssertEqual(model.packageIdentity, "swift-bar")
Expand All @@ -89,7 +91,8 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase {
platformBuildInfo: nil,
weightedKeywords: [],
swift6Readiness: nil,
forkedFromInfo: nil))
forkedFromInfo: nil,
customCollections: []))

// validate
XCTAssertEqual(model.documentationTarget, .internal(docVersion: .reference("main"), archive: "archive1"))
Expand All @@ -116,7 +119,8 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase {
platformBuildInfo: nil,
weightedKeywords: [],
swift6Readiness: nil,
forkedFromInfo: nil))
forkedFromInfo: nil,
customCollections: []))

// validate
XCTAssertEqual(model.documentationTarget, .external(url: "https://example.com/package/documentation"))
Expand Down
24 changes: 24 additions & 0 deletions Tests/AppTests/CustomCollectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class CustomCollectionTests: AppTestCase {
}

func test_CustomCollection_packages() async throws {
// Test CustomCollection.packages relation
// setup
let p1 = Package(id: .id0, url: "1".asGithubUrl.url)
try await p1.save(on: app.db)
Expand All @@ -154,6 +155,29 @@ class CustomCollectionTests: AppTestCase {
}
}

func test_Package_customCollections() async throws {
// Test Package.customCollections relation
// setup
let p1 = Package(id: .id0, url: "1".asGithubUrl.url)
try await p1.save(on: app.db)
do {
let collection = CustomCollection(id: .id1, .init(name: "List 1", url: "https://github.com/foo/bar/list-1.json"))
try await collection.save(on: app.db)
try await collection.$packages.attach(p1, on: app.db)
}
do {
let collection = CustomCollection(id: .id2, .init(name: "List 2", url: "https://github.com/foo/bar/list-2.json"))
try await collection.save(on: app.db)
try await collection.$packages.attach(p1, on: app.db)
}

do { // MUT
let pkg = try await Package.find(.id0, on: app.db).unwrap()
try await pkg.$customCollections.load(on: app.db)
XCTAssertEqual(Set(pkg.customCollections.map(\.id)) , Set([.id1, .id2]))
}
}

func test_CustomCollection_cascade() async throws {
// setup
let pkg = Package(id: .id0, url: "1".asGithubUrl.url)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ extension API.PackageController.GetRoute.Model {
releaseReference: .tag(5, 2, 0),
preReleaseReference: .tag(5, 3, 0, "beta.1"),
swift6Readiness: nil,
forkedFromInfo: nil
forkedFromInfo: nil,
customCollections: []
)
}
}
Loading