From a8a21664f5f9107a57119b67c618642fe469cf6f Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Mon, 9 Sep 2024 11:49:31 +0200 Subject: [PATCH 01/21] Add forked from list item on package view --- ...API+PackageController+GetRoute+Model.swift | 11 +++++++--- .../API/API+PackageController+GetRoute.swift | 21 ++++++++++++++++++- .../Controllers/API/Types+WithExample.swift | 3 ++- Sources/App/Models/Repository.swift | 7 +++++++ .../GetRoute.Model+ext.swift | 19 +++++++++++++++++ .../PackageController/PackageShow+View.swift | 1 + 6 files changed, 57 insertions(+), 5 deletions(-) diff --git a/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift b/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift index 211e5fb17..38ee33c05 100644 --- a/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift +++ b/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift @@ -50,6 +50,7 @@ extension API.PackageController.GetRoute { var releaseReferences: [App.Version.Kind: App.Reference] var fundingLinks: [FundingLink] var swift6Readiness: Swift6Readiness? + var forkedFromURL: String? internal init(packageId: Package.Id, repositoryOwner: String, @@ -81,7 +82,8 @@ extension API.PackageController.GetRoute { releaseReference: App.Reference?, preReleaseReference: App.Reference?, fundingLinks: [FundingLink] = [], - swift6Readiness: Swift6Readiness? + swift6Readiness: Swift6Readiness?, + forkedFromURL: String? ) { self.packageId = packageId self.repositoryOwner = repositoryOwner @@ -123,6 +125,7 @@ extension API.PackageController.GetRoute { }() self.fundingLinks = fundingLinks self.swift6Readiness = swift6Readiness + self.forkedFromURL = forkedFromURL } init?(result: API.PackageController.PackageResult, @@ -132,7 +135,8 @@ extension API.PackageController.GetRoute { swiftVersionBuildInfo: BuildInfo?, platformBuildInfo: BuildInfo?, weightedKeywords: [WeightedKeyword] = [], - swift6Readiness: Swift6Readiness?) { + swift6Readiness: Swift6Readiness?, + forkedFromURL: String?) { // we consider certain attributes as essential and return nil (raising .notFound) let repository = result.repository guard @@ -177,7 +181,8 @@ extension API.PackageController.GetRoute { releaseReference: result.releaseVersion?.reference, preReleaseReference: result.preReleaseVersion?.reference, fundingLinks: result.repository.fundingLinks, - swift6Readiness: swift6Readiness + swift6Readiness: swift6Readiness, + forkedFromURL: forkedFromURL ) } diff --git a/Sources/App/Controllers/API/API+PackageController+GetRoute.swift b/Sources/App/Controllers/API/API+PackageController+GetRoute.swift index 1a7d6750a..92d0e0d77 100644 --- a/Sources/App/Controllers/API/API+PackageController+GetRoute.swift +++ b/Sources/App/Controllers/API/API+PackageController+GetRoute.swift @@ -30,6 +30,10 @@ extension API.PackageController { let packageResult = try await PackageResult.query(on: database, owner: owner, repository: repository) + + let forkedFromURL = try await self.fetchForkedFromURL(on: database, + repository: packageResult.repository) + async let weightedKeywords = WeightedKeyword.query( on: database, keywords: packageResult.repository.keywords ) @@ -55,7 +59,8 @@ extension API.PackageController { swiftVersionBuildInfo: buildInfo.swiftVersion, platformBuildInfo: buildInfo.platform, weightedKeywords: weightedKeywords, - swift6Readiness: buildInfo.swift6Readiness + swift6Readiness: buildInfo.swift6Readiness, + forkedFromURL: forkedFromURL ), let schema = API.PackageSchema(result: packageResult) else { @@ -64,6 +69,20 @@ extension API.PackageController { return (model, schema) } + + private static func fetchForkedFromURL(on database: Database, repository: Repository) async throws -> String? { + if let forkedFrom = repository.forkedFrom { + switch forkedFrom { + case .parentId(let id): + let repo = try await Repository.find(on: database, for: id) + guard let owner = repo?.owner, let name = repo?.name else { return nil } + return SiteURL.package(.value(owner), .value(name), nil).absoluteURL() + case .parentURL(let string): + return string + } + } + return nil + } } } diff --git a/Sources/App/Controllers/API/Types+WithExample.swift b/Sources/App/Controllers/API/Types+WithExample.swift index 1741e5dec..107dba9ee 100644 --- a/Sources/App/Controllers/API/Types+WithExample.swift +++ b/Sources/App/Controllers/API/Types+WithExample.swift @@ -247,7 +247,8 @@ extension API.PackageController.GetRoute.Model: WithExample { defaultBranchReference: .branch("main"), releaseReference: .tag(1, 2, 3, "1.2.3"), preReleaseReference: nil, - swift6Readiness: nil) + swift6Readiness: nil, + forkedFromURL: nil) } } diff --git a/Sources/App/Models/Repository.swift b/Sources/App/Models/Repository.swift index 20ab78a28..d318b047d 100644 --- a/Sources/App/Models/Repository.swift +++ b/Sources/App/Models/Repository.swift @@ -224,6 +224,13 @@ final class Repository: @unchecked Sendable, Model, Content { .filter(\.$package.$id == pkgId) .first() ?? Repository(packageId: pkgId) } + + static func find(on database: Database, for packageId: Package.Id) async throws -> Repository? { + return try await Repository.query(on: database) + .filter(\.$package.$id == packageId) + .first() + } + } diff --git a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift index d4e77d1ef..060fd0103 100644 --- a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift +++ b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift @@ -181,6 +181,25 @@ extension API.PackageController.GetRoute.Model { return .empty } } + + func forkedListItem() -> Node { + if let forkedFromURL { + let repoURLNode: Node = .a( + .href(forkedFromURL), + .text("repository") + ) + return .li( + .class("forked"), + .group( + .text("Forked from "), + repoURLNode, + .text(".") + ) + ) + } else { + return .empty + } + } func binaryTargetsItem() -> Node { guard hasBinaryTargets else { return .empty } diff --git a/Sources/App/Views/PackageController/PackageShow+View.swift b/Sources/App/Views/PackageController/PackageShow+View.swift index 37f4a4d84..271e68543 100644 --- a/Sources/App/Views/PackageController/PackageShow+View.swift +++ b/Sources/App/Views/PackageController/PackageShow+View.swift @@ -167,6 +167,7 @@ extension PackageShow { .ul( .class("main-metadata"), model.archivedListItem(), + model.forkedListItem(), model.authorsListItem(), model.binaryTargetsItem(), model.historyListItem(), From 287172faeca6085c927e62e5b44860002f162191 Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Mon, 9 Sep 2024 14:12:35 +0200 Subject: [PATCH 02/21] Fix up tests --- ...ackageController+GetRoute+ModelTests.swift | 29 ++++++++++++++++--- ...ackageController.GetRoute.Model+mock.swift | 3 +- .../test_forked_from.1.txt | 3 ++ 3 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from.1.txt diff --git a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift index bff7618a4..69bf96ba0 100644 --- a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift +++ b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift @@ -42,7 +42,8 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { swiftVersionBuildInfo: nil, platformBuildInfo: nil, weightedKeywords: [], - swift6Readiness: nil) + swift6Readiness: nil, + forkedFromURL: nil) // validate XCTAssertNotNil(m) @@ -64,7 +65,9 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { swiftVersionBuildInfo: nil, platformBuildInfo: nil, weightedKeywords: [], - swift6Readiness: nil)) + swift6Readiness: nil, + forkedFromURL: nil + )) // validate XCTAssertEqual(model.packageIdentity, "swift-bar") @@ -86,7 +89,9 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { swiftVersionBuildInfo: nil, platformBuildInfo: nil, weightedKeywords: [], - swift6Readiness: nil)) + swift6Readiness: nil, + forkedFromURL: nil + )) // validate XCTAssertEqual(model.documentationTarget, .internal(docVersion: .reference("main"), archive: "archive1")) @@ -112,7 +117,9 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { swiftVersionBuildInfo: nil, platformBuildInfo: nil, weightedKeywords: [], - swift6Readiness: nil)) + swift6Readiness: nil, + forkedFromURL: nil + )) // validate XCTAssertEqual(model.documentationTarget, .external(url: "https://example.com/package/documentation")) @@ -144,6 +151,13 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { let renderedHistory = model.historyListItem().render(indentedBy: .spaces(2)) assertSnapshot(of: renderedHistory, as: .lines) } + + func test_forked_from() throws { + var model = API.PackageController.GetRoute.Model.mock + model.forkedFromURL = "https://github.com/owner/repository.git" + let renderedForkedFrom = model.forkedListItem().render(indentedBy: .spaces(2)) + assertSnapshot(of: renderedForkedFrom, as: .lines) + } func test_binary_targets() throws { var model = API.PackageController.GetRoute.Model.mock @@ -329,6 +343,13 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { .fromSPIManifest("By Author One, Author Two, and more!") XCTAssertEqual(model.authorsListItem().render(), "
  • By Author One, Author Two, and more!
  • ") } + + func test_forkedFrom_formatting() throws { + var model = API.PackageController.GetRoute.Model.mock + model.forkedFromURL = "https://github.com/owner/repository.git" + let renderedForkedFrom = model.forkedListItem().render() + XCTAssertEqual(renderedForkedFrom, "
  • Forked from repository.
  • ") + } func test_BuildInfo_init() throws { // ensure nil propagation when all versions' values are nil diff --git a/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift b/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift index cafa1c83d..81f9eb71b 100644 --- a/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift +++ b/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift @@ -125,7 +125,8 @@ extension API.PackageController.GetRoute.Model { defaultBranchReference: .branch("main"), releaseReference: .tag(5, 2, 0), preReleaseReference: .tag(5, 3, 0, "beta.1"), - swift6Readiness: nil + swift6Readiness: nil, + forkedFromURL: nil ) } } diff --git a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from.1.txt b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from.1.txt new file mode 100644 index 000000000..620ce3bbe --- /dev/null +++ b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from.1.txt @@ -0,0 +1,3 @@ +
  • Forked from + repository. +
  • \ No newline at end of file From 489b423071a05ab60ac40488fade3143205c7ed3 Mon Sep 17 00:00:00 2001 From: Dave Verwer Date: Mon, 9 Sep 2024 15:53:33 +0100 Subject: [PATCH 03/21] Added Icon and style for `forked` metadata item. --- FrontEnd/styles/images.scss | 2 ++ FrontEnd/styles/package.scss | 5 +++++ Resources/SVGs/fork~dark.svg | 1 + Resources/SVGs/fork~light.svg | 1 + 4 files changed, 9 insertions(+) create mode 100644 Resources/SVGs/fork~dark.svg create mode 100644 Resources/SVGs/fork~light.svg diff --git a/FrontEnd/styles/images.scss b/FrontEnd/styles/images.scss index 4d1662990..de3954111 100644 --- a/FrontEnd/styles/images.scss +++ b/FrontEnd/styles/images.scss @@ -34,6 +34,7 @@ --image-download: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iIzJmMmYyZiI+PHBhdGggZD0ibTM3LjkzIDI1LjAxLTIuOTgtNC4wMS03LjQ1IDUuNTN2LTIzLjA5aC01djIzLjA5bC03LjQ1LTUuNTMtMi45OCA0LjAxIDEyLjkzIDkuNjF6Ii8+PHBhdGggZD0ibTQyLjMxIDMzLjA5djguNDdoLTM0LjYydi04LjQ3aC01djEzLjQ3aDQ0LjYydi0xMy40N3oiLz48L2c+PC9zdmc+'); --image-error: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTI1IDFjLTEzLjI1IDAtMjQgMTAuNzUtMjQgMjRzMTAuNzUgMjQgMjQgMjQgMjQtMTAuNzUgMjQtMjQtMTAuNzUtMjQtMjQtMjR6bTIuNjYgMzYuMThjLS42NC41OS0xLjUzLjg5LTIuNjguODlzLTIuMDctLjMtMi43Mi0uODktLjk4LTEuNDItLjk4LTIuNDcuMzItMS45Ljk1LTIuNDkgMS41NS0uODkgMi43Ni0uODkgMi4xLjMgMi43Mi44OWMuNjEuNTkuOTIgMS40Mi45MiAyLjQ5cy0uMzIgMS44OC0uOTYgMi40N3ptLjI0LTguNjJoLTUuOGwtLjktMTYuNjNoNy41OWwtLjkgMTYuNjN6IiBmaWxsPSIjYzQ0Ii8+PC9zdmc+'); --image-executables: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTI1IDFjLTEzLjI1IDAtMjQgMTAuNzUtMjQgMjRzMTAuNzUgMjQgMjQgMjQgMjQtMTAuNzUgMjQtMjQtMTAuNzUtMjQtMjQtMjR6bS0xMi45NCAzMy45NSA3LjM5LTkuOTUtNy4zOS05Ljk1IDQuMDEtMi45OCA5LjYxIDEyLjkzLTkuNjEgMTIuOTN6bTI0Ljg4IDMuODNoLTEyLjV2LTVoMTIuNXoiIGZpbGw9IiMyZjJmMmYiLz48L3N2Zz4='); + --image-fork: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTQzLjUgMTEuODdjMC0zLjU5LTIuOTEtNi41LTYuNS02LjVzLTYuNSAyLjkxLTYuNSA2LjVjMCAyLjQgMS4zMSA0LjUgMy4yNSA1LjYydjExLjM3bC0yMC4xNiA1Ljg1IDEuODEgNi4yNCAyNC44NS03LjIxdi0xNi4yNWMxLjk0LTEuMTIgMy4yNS0zLjIyIDMuMjUtNS42MnoiIGZpbGw9IiMyYjJiMmIiLz48cGF0aCBkPSJtMjIuNSAzNy45M2MwLTMuMjYtMS45NS02LjA2LTQuNzUtNy4zMXYtMjkuNjJoLTYuNXYyOS42M2MtMi44IDEuMjUtNC43NSA0LjA1LTQuNzUgNy4zMXMxLjk1IDYuMDYgNC43NSA3LjMxdjMuNzZoNi41di0zLjc2YzIuOC0xLjI1IDQuNzUtNC4wNSA0Ljc1LTcuMzF6IiBmaWxsPSIjNmI2YjZiIi8+PC9zdmc+'); --image-ghcta-header: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTIwIDUwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Im0yNSAxLjU5Yy0xMy4yNSAwLTI0IDEwLjc1LTI0IDI0IDAgMTAuNiA2Ljg4IDE5LjYgMTYuNDEgMjIuNzcgMS4yLjIyIDEuNjQtLjUyIDEuNjQtMS4xNiAwLS41Ny0uMDItMi4wOC0uMDMtNC4wOC02LjY4IDEuNDUtOC4wOC0zLjIyLTguMDgtMy4yMi0xLjA5LTIuNzctMi42Ni0zLjUxLTIuNjYtMy41MS0yLjE4LTEuNDkuMTYtMS40Ni4xNi0xLjQ2IDIuNDEuMTcgMy42OCAyLjQ3IDMuNjggMi40NyAyLjE0IDMuNjcgNS42MiAyLjYxIDYuOTggMS45OS4yMi0xLjU1Ljg0LTIuNjEgMS41Mi0zLjIxLTUuMzMtLjYxLTEwLjkzLTIuNjYtMTAuOTMtMTEuODYgMC0yLjYyLjk0LTQuNzYgMi40Ny02LjQ0LS4yNS0uNjEtMS4wNy0zLjA1LjI0LTYuMzUgMCAwIDIuMDItLjY1IDYuNiAyLjQ2IDEuOTEtLjUzIDMuOTctLjggNi4wMS0uODEgMi4wNCAwIDQuMDkuMjggNi4wMS44MSA0LjU4LTMuMTEgNi41OS0yLjQ2IDYuNTktMi40NiAxLjMxIDMuMy40OSA1Ljc0LjI0IDYuMzUgMS41NCAxLjY4IDIuNDcgMy44MiAyLjQ3IDYuNDQgMCA5LjIyLTUuNjEgMTEuMjUtMTAuOTYgMTEuODQuODYuNzQgMS42MyAyLjIxIDEuNjMgNC40NCAwIDMuMjEtLjAzIDUuOC0uMDMgNi41OCAwIC42NC40MyAxLjM5IDEuNjUgMS4xNSA5LjUzLTMuMTggMTYuNC0xMi4xNyAxNi40LTIyLjc3IDAtMTMuMjYtMTAuNzUtMjQtMjQtMjR6IiBmaWxsPSIjMmYyZjJmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48Y2lyY2xlIGN4PSI5NSIgY3k9IjI1IiBmaWxsPSIjMmYyZjJmIiByPSIyNCIvPjxwYXRoIGQ9Im0xMDIuNjIgMTMuNTFjLTMuMTctLjItNS45OSAxLjMzLTcuNjIgMy43My0xLjY0LTIuNC00LjQ2LTMuOTMtNy42Mi0zLjczLTQuODEuMy04LjM0IDQuNzYtNy45NyA5LjU2LjE5IDIuNDcgMS41NCA3LjIzIDExLjE5IDEzLjQ3bDQuNCAyLjg1IDQuNC0yLjg1YzkuNjQtNi4yMyAxMS0xMSAxMS4xOS0xMy40Ny4zNy00LjgtMy4xNi05LjI2LTcuOTctOS41NnoiIGZpbGw9IiNmNDRlYjIiLz48cGF0aCBkPSJtNTQuNDMgMjMuN2g0LjI2di0zLjk1aDIuNjF2My45NWg0LjI3djIuNjJoLTQuMjd2My45NGgtMi42MXYtMy45NGgtNC4yNnoiIGZpbGw9IiMyZjJmMmYiLz48L3N2Zz4='); --image-github: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTI1IDEuNTljLTEzLjI1IDAtMjQgMTAuNzUtMjQgMjQgMCAxMC42IDYuODggMTkuNiAxNi40MSAyMi43NyAxLjIuMjIgMS42NC0uNTIgMS42NC0xLjE2IDAtLjU3LS4wMi0yLjA4LS4wMy00LjA4LTYuNjggMS40NS04LjA4LTMuMjItOC4wOC0zLjIyLTEuMDktMi43Ny0yLjY2LTMuNTEtMi42Ni0zLjUxLTIuMTgtMS40OS4xNi0xLjQ2LjE2LTEuNDYgMi40MS4xNyAzLjY4IDIuNDcgMy42OCAyLjQ3IDIuMTQgMy42NyA1LjYyIDIuNjEgNi45OCAxLjk5LjIyLTEuNTUuODQtMi42MSAxLjUyLTMuMjEtNS4zMy0uNjEtMTAuOTMtMi42Ni0xMC45My0xMS44NiAwLTIuNjIuOTQtNC43NiAyLjQ3LTYuNDQtLjI1LS42MS0xLjA3LTMuMDUuMjQtNi4zNSAwIDAgMi4wMi0uNjUgNi42IDIuNDYgMS45MS0uNTMgMy45Ny0uOCA2LjAxLS44MSAyLjA0IDAgNC4wOS4yOCA2LjAxLjgxIDQuNTgtMy4xMSA2LjU5LTIuNDYgNi41OS0yLjQ2IDEuMzEgMy4zLjQ5IDUuNzQuMjQgNi4zNSAxLjU0IDEuNjggMi40NyAzLjgyIDIuNDcgNi40NCAwIDkuMjItNS42MSAxMS4yNS0xMC45NiAxMS44NC44Ni43NCAxLjYzIDIuMjEgMS42MyA0LjQ0IDAgMy4yMS0uMDMgNS44LS4wMyA2LjU4IDAgLjY0LjQzIDEuMzkgMS42NSAxLjE1IDkuNTMtMy4xOCAxNi40LTEyLjE3IDE2LjQtMjIuNzcgMC0xMy4yNi0xMC43NS0yNC0yNC0yNHoiIGZpbGw9IiMyZjJmMmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg=='); --image-heart: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTM2LjgyIDQuOTZjLTQuOTEtLjMxLTkuMjggMi4wNi0xMS44MiA1Ljc4LTIuNTQtMy43Mi02LjkxLTYuMDktMTEuODItNS43OC03LjQ1LjQ2LTEyLjkzIDcuMzctMTIuMzUgMTQuODIuMjkgMy44MyAyLjM5IDExLjIxIDE3LjM1IDIwLjg4bDYuODMgNC40MSA2LjgzLTQuNDFjMTQuOTUtOS42NiAxNy4wNS0xNy4wNSAxNy4zNS0yMC44OC41Ny03LjQ0LTQuOS0xNC4zNS0xMi4zNS0xNC44MnoiIGZpbGw9IiNmNDRlYjIiLz48L3N2Zz4='); @@ -73,6 +74,7 @@ --image-download: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0iI2YxZjFmMSI+PHBhdGggZD0ibTM3LjkzIDI1LjAxLTIuOTgtNC4wMS03LjQ1IDUuNTN2LTIzLjA5aC01djIzLjA5bC03LjQ1LTUuNTMtMi45OCA0LjAxIDEyLjkzIDkuNjF6Ii8+PHBhdGggZD0ibTQyLjMxIDMzLjA5djguNDdoLTM0LjYydi04LjQ3aC01djEzLjQ3aDQ0LjYydi0xMy40N3oiLz48L2c+PC9zdmc+'); --image-error: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTI1IDFjLTEzLjI1IDAtMjQgMTAuNzUtMjQgMjRzMTAuNzUgMjQgMjQgMjQgMjQtMTAuNzUgMjQtMjQtMTAuNzUtMjQtMjQtMjR6bTIuNjYgMzYuMThjLS42NC41OS0xLjUzLjg5LTIuNjguODlzLTIuMDctLjMtMi43Mi0uODktLjk4LTEuNDItLjk4LTIuNDcuMzItMS45Ljk1LTIuNDkgMS41NS0uODkgMi43Ni0uODkgMi4xLjMgMi43Mi44OWMuNjEuNTkuOTIgMS40Mi45MiAyLjQ5cy0uMzIgMS44OC0uOTYgMi40N3ptLjI0LTguNjJoLTUuOGwtLjktMTYuNjNoNy41OWwtLjkgMTYuNjN6IiBmaWxsPSIjZmY0MzQzIi8+PC9zdmc+'); --image-executables: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTI1IDFjLTEzLjI1IDAtMjQgMTAuNzUtMjQgMjRzMTAuNzUgMjQgMjQgMjQgMjQtMTAuNzUgMjQtMjQtMTAuNzUtMjQtMjQtMjR6bS0xMi45NCAzMy45NSA3LjM5LTkuOTUtNy4zOS05Ljk1IDQuMDEtMi45OCA5LjYxIDEyLjkzLTkuNjEgMTIuOTN6bTI0Ljg4IDMuODNoLTEyLjV2LTVoMTIuNXoiIGZpbGw9IiNmMWYxZjEiLz48L3N2Zz4='); + --image-fork: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTQzLjUgMTEuODdjMC0zLjU5LTIuOTEtNi41LTYuNS02LjVzLTYuNSAyLjkxLTYuNSA2LjVjMCAyLjQgMS4zMSA0LjUgMy4yNSA1LjYydjExLjM3bC0yMC4xNiA1Ljg1IDEuODEgNi4yNCAyNC44NS03LjIxdi0xNi4yNWMxLjk0LTEuMTIgMy4yNS0zLjIyIDMuMjUtNS42MnoiIGZpbGw9IiNmMWYxZjEiLz48cGF0aCBkPSJtMjIuNSAzNy45M2MwLTMuMjYtMS45NS02LjA2LTQuNzUtNy4zMXYtMjkuNjJoLTYuNXYyOS42M2MtMi44IDEuMjUtNC43NSA0LjA1LTQuNzUgNy4zMXMxLjk1IDYuMDYgNC43NSA3LjMxdjMuNzZoNi41di0zLjc2YzIuOC0xLjI1IDQuNzUtNC4wNSA0Ljc1LTcuMzF6IiBmaWxsPSIjYjFiMWIxIi8+PC9zdmc+'); --image-ghcta-header: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTIwIDUwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Im0yNSAxLjU5Yy0xMy4yNSAwLTI0IDEwLjc1LTI0IDI0IDAgMTAuNiA2Ljg4IDE5LjYgMTYuNDEgMjIuNzcgMS4yLjIyIDEuNjQtLjUyIDEuNjQtMS4xNiAwLS41Ny0uMDItMi4wOC0uMDMtNC4wOC02LjY4IDEuNDUtOC4wOC0zLjIyLTguMDgtMy4yMi0xLjA5LTIuNzctMi42Ni0zLjUxLTIuNjYtMy41MS0yLjE4LTEuNDkuMTYtMS40Ni4xNi0xLjQ2IDIuNDEuMTcgMy42OCAyLjQ3IDMuNjggMi40NyAyLjE0IDMuNjcgNS42MiAyLjYxIDYuOTggMS45OS4yMi0xLjU1Ljg0LTIuNjEgMS41Mi0zLjIxLTUuMzMtLjYxLTEwLjkzLTIuNjYtMTAuOTMtMTEuODYgMC0yLjYyLjk0LTQuNzYgMi40Ny02LjQ0LS4yNS0uNjEtMS4wNy0zLjA1LjI0LTYuMzUgMCAwIDIuMDItLjY1IDYuNiAyLjQ2IDEuOTEtLjUzIDMuOTctLjggNi4wMS0uODEgMi4wNCAwIDQuMDkuMjggNi4wMS44MSA0LjU4LTMuMTEgNi41OS0yLjQ2IDYuNTktMi40NiAxLjMxIDMuMy40OSA1Ljc0LjI0IDYuMzUgMS41NCAxLjY4IDIuNDcgMy44MiAyLjQ3IDYuNDQgMCA5LjIyLTUuNjEgMTEuMjUtMTAuOTYgMTEuODQuODYuNzQgMS42MyAyLjIxIDEuNjMgNC40NCAwIDMuMjEtLjAzIDUuOC0uMDMgNi41OCAwIC42NC40MyAxLjM5IDEuNjUgMS4xNSA5LjUzLTMuMTggMTYuNC0xMi4xNyAxNi40LTIyLjc3IDAtMTMuMjYtMTAuNzUtMjQtMjQtMjR6IiBmaWxsPSIjZjFmMWYxIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz48Y2lyY2xlIGN4PSI5NSIgY3k9IjI1IiBmaWxsPSIjZjFmMWYxIiByPSIyNCIvPjxwYXRoIGQ9Im0xMDIuNjIgMTMuNTFjLTMuMTctLjItNS45OSAxLjMzLTcuNjIgMy43My0xLjY0LTIuNC00LjQ2LTMuOTMtNy42Mi0zLjczLTQuODEuMy04LjM0IDQuNzYtNy45NyA5LjU2LjE5IDIuNDcgMS41NCA3LjIzIDExLjE5IDEzLjQ3bDQuNCAyLjg1IDQuNC0yLjg1YzkuNjQtNi4yMyAxMS0xMSAxMS4xOS0xMy40Ny4zNy00LjgtMy4xNi05LjI2LTcuOTctOS41NnoiIGZpbGw9IiNmNDRlYjIiLz48cGF0aCBkPSJtNTQuNDMgMjMuN2g0LjI2di0zLjk1aDIuNjF2My45NWg0LjI3djIuNjJoLTQuMjd2My45NGgtMi42MXYtMy45NGgtNC4yNnoiIGZpbGw9IiNmMWYxZjEiLz48L3N2Zz4='); --image-github: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTI1IDEuNTljLTEzLjI1IDAtMjQgMTAuNzUtMjQgMjQgMCAxMC42IDYuODggMTkuNiAxNi40MSAyMi43NyAxLjIuMjIgMS42NC0uNTIgMS42NC0xLjE2IDAtLjU3LS4wMi0yLjA4LS4wMy00LjA4LTYuNjggMS40NS04LjA4LTMuMjItOC4wOC0zLjIyLTEuMDktMi43Ny0yLjY2LTMuNTEtMi42Ni0zLjUxLTIuMTgtMS40OS4xNi0xLjQ2LjE2LTEuNDYgMi40MS4xNyAzLjY4IDIuNDcgMy42OCAyLjQ3IDIuMTQgMy42NyA1LjYyIDIuNjEgNi45OCAxLjk5LjIyLTEuNTUuODQtMi42MSAxLjUyLTMuMjEtNS4zMy0uNjEtMTAuOTMtMi42Ni0xMC45My0xMS44NiAwLTIuNjIuOTQtNC43NiAyLjQ3LTYuNDQtLjI1LS42MS0xLjA3LTMuMDUuMjQtNi4zNSAwIDAgMi4wMi0uNjUgNi42IDIuNDYgMS45MS0uNTMgMy45Ny0uOCA2LjAxLS44MSAyLjA0IDAgNC4wOS4yOCA2LjAxLjgxIDQuNTgtMy4xMSA2LjU5LTIuNDYgNi41OS0yLjQ2IDEuMzEgMy4zLjQ5IDUuNzQuMjQgNi4zNSAxLjU0IDEuNjggMi40NyAzLjgyIDIuNDcgNi40NCAwIDkuMjItNS42MSAxMS4yNS0xMC45NiAxMS44NC44Ni43NCAxLjYzIDIuMjEgMS42MyA0LjQ0IDAgMy4yMS0uMDMgNS44LS4wMyA2LjU4IDAgLjY0LjQzIDEuMzkgMS42NSAxLjE1IDkuNTMtMy4xOCAxNi40LTEyLjE3IDE2LjQtMjIuNzcgMC0xMy4yNi0xMC43NS0yNC0yNC0yNHoiIGZpbGw9IiNmMWYxZjEiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg=='); --image-heart: url('data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNTAgNTAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTM2LjgyIDQuOTZjLTQuOTEtLjMxLTkuMjggMi4wNi0xMS44MiA1Ljc4LTIuNTQtMy43Mi02LjkxLTYuMDktMTEuODItNS43OC03LjQ1LjQ2LTEyLjkzIDcuMzctMTIuMzUgMTQuODIuMjkgMy44MyAyLjM5IDExLjIxIDE3LjM1IDIwLjg4bDYuODMgNC40MSA2LjgzLTQuNDFjMTQuOTUtOS42NiAxNy4wNS0xNy4wNSAxNy4zNS0yMC44OC41Ny03LjQ0LTQuOS0xNC4zNS0xMi4zNS0xNC44MnoiIGZpbGw9IiNmNDRlYjIiLz48L3N2Zz4='); diff --git a/FrontEnd/styles/package.scss b/FrontEnd/styles/package.scss index b32d43b54..28ee28c6b 100644 --- a/FrontEnd/styles/package.scss +++ b/FrontEnd/styles/package.scss @@ -75,6 +75,11 @@ background-image: var(--image-warning); } + li.forked { + grid-column-start: span 2; + background-image: var(--image-fork); + } + li.authors { grid-column-start: span 2; background-image: var(--image-authors); diff --git a/Resources/SVGs/fork~dark.svg b/Resources/SVGs/fork~dark.svg new file mode 100644 index 000000000..c4ba52a15 --- /dev/null +++ b/Resources/SVGs/fork~dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Resources/SVGs/fork~light.svg b/Resources/SVGs/fork~light.svg new file mode 100644 index 000000000..534c94039 --- /dev/null +++ b/Resources/SVGs/fork~light.svg @@ -0,0 +1 @@ + \ No newline at end of file From 2e2cac45988cf4ded38248e2ce2e13621e8878ac Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Tue, 10 Sep 2024 13:11:58 +0200 Subject: [PATCH 04/21] Improve ForkedFrom to reflect more info in UI --- ...I+PackageController+ForkedFromResult.swift | 42 +++++++++++++++ ...API+PackageController+GetRoute+Model.swift | 52 ++++++++++++++++--- .../API/API+PackageController+GetRoute.swift | 15 +++--- .../Controllers/API/Types+WithExample.swift | 3 +- .../Core/Query+Support/Joined3+Package.swift | 8 +++ .../GetRoute.Model+ext.swift | 47 +++++++++++++---- ...ackageController.GetRoute.Model+mock.swift | 3 +- 7 files changed, 144 insertions(+), 26 deletions(-) create mode 100644 Sources/App/Controllers/API/API+PackageController+ForkedFromResult.swift diff --git a/Sources/App/Controllers/API/API+PackageController+ForkedFromResult.swift b/Sources/App/Controllers/API/API+PackageController+ForkedFromResult.swift new file mode 100644 index 000000000..54b980ac1 --- /dev/null +++ b/Sources/App/Controllers/API/API+PackageController+ForkedFromResult.swift @@ -0,0 +1,42 @@ +// 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 +import Vapor + +extension API.PackageController { + enum ForkedFromResult { + case fromSPI(repository: String, owner: String, ownerName: String, packageName: String) + case fromGitHub(url: String) + + static func query(on database: Database, packageId: Package.Id) async throws -> ForkedFromResult? { + let model = try await Joined3.query(on: database, packageId: packageId).first() + + guard let repoName = model?.repository.name, + let ownerName = model?.repository.ownerName, + let owner = model?.repository.owner, + let packageName = model?.version.packageName else { + return nil + } + + return ForkedFromResult.fromSPI( + repository: repoName, + owner: owner, + ownerName: ownerName, + packageName: packageName + ) + } + } +} diff --git a/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift b/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift index 38ee33c05..0662bd93a 100644 --- a/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift +++ b/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift @@ -20,6 +20,7 @@ import Vapor extension API.PackageController.GetRoute { struct Model: Content, Equatable { var packageId: Package.Id + var packageName: String var repositoryOwner: String var repositoryOwnerName: String var repositoryName: String @@ -50,9 +51,10 @@ extension API.PackageController.GetRoute { var releaseReferences: [App.Version.Kind: App.Reference] var fundingLinks: [FundingLink] var swift6Readiness: Swift6Readiness? - var forkedFromURL: String? + var forkedFromInfo: ForkedFromInfo? internal init(packageId: Package.Id, + packageName: String, repositoryOwner: String, repositoryOwnerName: String, repositoryName: String, @@ -83,9 +85,10 @@ extension API.PackageController.GetRoute { preReleaseReference: App.Reference?, fundingLinks: [FundingLink] = [], swift6Readiness: Swift6Readiness?, - forkedFromURL: String? + forkedFromResult: API.PackageController.ForkedFromResult? ) { self.packageId = packageId + self.packageName = packageName self.repositoryOwner = repositoryOwner self.repositoryOwnerName = repositoryOwnerName self.repositoryName = repositoryName @@ -125,7 +128,22 @@ extension API.PackageController.GetRoute { }() self.fundingLinks = fundingLinks self.swift6Readiness = swift6Readiness - self.forkedFromURL = forkedFromURL + if let forkedFromResult { + switch forkedFromResult { + case .fromSPI(let repo, let owner, let ownerName, let packageName): + self.forkedFromInfo = ForkedFromInfo.fromSPI( + packageName: packageName, + originalOwner: owner, + originalOwnerName: ownerName, + originalRepo: repo, + originalPackageName: packageName + ) + case .fromGitHub(let url): + self.forkedFromInfo = ForkedFromInfo.fromGitHub(url: url) + } + } else { + self.forkedFromInfo = nil + } } init?(result: API.PackageController.PackageResult, @@ -136,18 +154,20 @@ extension API.PackageController.GetRoute { platformBuildInfo: BuildInfo?, weightedKeywords: [WeightedKeyword] = [], swift6Readiness: Swift6Readiness?, - forkedFromURL: String?) { + forkedFromResult: API.PackageController.ForkedFromResult?) { // we consider certain attributes as essential and return nil (raising .notFound) let repository = result.repository guard let repositoryOwner = repository.owner, let repositoryOwnerName = repository.ownerDisplayName, let repositoryName = repository.name, - let packageId = result.package.id + let packageId = result.package.id, + let packageName = result.defaultBranchVersion.packageName else { return nil } self.init( packageId: packageId, + packageName: packageName, repositoryOwner: repositoryOwner, repositoryOwnerName: repositoryOwnerName, repositoryName: repositoryName, @@ -182,7 +202,7 @@ extension API.PackageController.GetRoute { preReleaseReference: result.preReleaseVersion?.reference, fundingLinks: result.repository.fundingLinks, swift6Readiness: swift6Readiness, - forkedFromURL: forkedFromURL + forkedFromResult: forkedFromResult ) } @@ -353,6 +373,26 @@ extension API.PackageController.GetRoute.Model { } } } + + enum ForkedFromInfo: Codable, Equatable { + case fromSPI( + packageName: String, + originalOwner: String, + originalOwnerName: String, + originalRepo: String, + originalPackageName: String + ) + case fromGitHub(url: String) + + var url: String { + switch self { + case .fromSPI(_, let originalOwner, _, let originalRepo, _): + return SiteURL.package(.value(originalOwner), .value(originalRepo), nil).absoluteURL() + case .fromGitHub(let url): + return url + } + } + } } diff --git a/Sources/App/Controllers/API/API+PackageController+GetRoute.swift b/Sources/App/Controllers/API/API+PackageController+GetRoute.swift index 92d0e0d77..02f4520e1 100644 --- a/Sources/App/Controllers/API/API+PackageController+GetRoute.swift +++ b/Sources/App/Controllers/API/API+PackageController+GetRoute.swift @@ -31,7 +31,7 @@ extension API.PackageController { owner: owner, repository: repository) - let forkedFromURL = try await self.fetchForkedFromURL(on: database, + let forkedFromResult = try? await self.fetchForkedFromResult(on: database, repository: packageResult.repository) async let weightedKeywords = WeightedKeyword.query( @@ -60,7 +60,7 @@ extension API.PackageController { platformBuildInfo: buildInfo.platform, weightedKeywords: weightedKeywords, swift6Readiness: buildInfo.swift6Readiness, - forkedFromURL: forkedFromURL + forkedFromResult: forkedFromResult ), let schema = API.PackageSchema(result: packageResult) else { @@ -70,15 +70,14 @@ extension API.PackageController { return (model, schema) } - private static func fetchForkedFromURL(on database: Database, repository: Repository) async throws -> String? { + private static func fetchForkedFromResult(on database: Database, repository: Repository) async throws -> ForkedFromResult? { if let forkedFrom = repository.forkedFrom { switch forkedFrom { case .parentId(let id): - let repo = try await Repository.find(on: database, for: id) - guard let owner = repo?.owner, let name = repo?.name else { return nil } - return SiteURL.package(.value(owner), .value(name), nil).absoluteURL() - case .parentURL(let string): - return string + let info = try await ForkedFromResult.query(on: database, packageId: id) + return info + case .parentURL(let url): + return .fromGitHub(url: url) } } return nil diff --git a/Sources/App/Controllers/API/Types+WithExample.swift b/Sources/App/Controllers/API/Types+WithExample.swift index 107dba9ee..4ca47f9c3 100644 --- a/Sources/App/Controllers/API/Types+WithExample.swift +++ b/Sources/App/Controllers/API/Types+WithExample.swift @@ -176,6 +176,7 @@ extension API.PackageController.GetRoute.Model.History: WithExample { extension API.PackageController.GetRoute.Model: WithExample { static var example: Self { .init(packageId: .example, + packageName: "Mona", repositoryOwner: "mona", repositoryOwnerName: "Mona", repositoryName: "LinkedList", @@ -248,7 +249,7 @@ extension API.PackageController.GetRoute.Model: WithExample { releaseReference: .tag(1, 2, 3, "1.2.3"), preReleaseReference: nil, swift6Readiness: nil, - forkedFromURL: nil) + forkedFromResult: nil) } } diff --git a/Sources/App/Core/Query+Support/Joined3+Package.swift b/Sources/App/Core/Query+Support/Joined3+Package.swift index 1b9d11a36..e10810b66 100644 --- a/Sources/App/Core/Query+Support/Joined3+Package.swift +++ b/Sources/App/Core/Query+Support/Joined3+Package.swift @@ -40,4 +40,12 @@ extension Joined3 where M == Package, R1 == Repository, R2 == Version { .filter(Repository.self, \.$owner, .custom("ilike"), owner) .filter(Repository.self, \.$name, .custom("ilike"), repository) } + + static func query(on database: Database, packageId: Package.Id) -> JoinedQueryBuilder { + query(on: database, + join: \Package.$id == \Repository.$package.$id, method: .inner, + join: \Version.$package.$id == \Package.$id, method: .inner) + .filter(Version.self, \Version.$latest == .defaultBranch) + .filter(Package.self, \Package.$id == packageId) + } } diff --git a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift index 060fd0103..665b82ce3 100644 --- a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift +++ b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift @@ -183,18 +183,45 @@ extension API.PackageController.GetRoute.Model { } func forkedListItem() -> Node { - if let forkedFromURL { - let repoURLNode: Node = .a( - .href(forkedFromURL), - .text("repository") - ) + if let forkedFromInfo { + let item: Node = { + switch forkedFromInfo { + case .fromGitHub(let url): + let repoURLNode: Node = .a( + .href(url), + .text("repository") + ) + return .group( + .text("Forked from "), + repoURLNode, + .text(".") + ) + case .fromSPI(let packageName, let owner, let ownerName, let repo, let originalPackageName): + let repoURLNode: Node + if packageName == originalPackageName { + repoURLNode = .a( + .href(forkedFromInfo.url), + .text(ownerName) + ) + + } else { + repoURLNode = .a( + .href(forkedFromInfo.url), + .text("\(originalPackageName) by \(ownerName)") + ) + } + + return .group( + .text("Forked from "), + repoURLNode, + .text(".") + ) + } + }() + return .li( .class("forked"), - .group( - .text("Forked from "), - repoURLNode, - .text(".") - ) + item ) } else { return .empty diff --git a/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift b/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift index 81f9eb71b..7244fb7b6 100644 --- a/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift +++ b/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift @@ -21,6 +21,7 @@ extension API.PackageController.GetRoute.Model { static var mock: Self { .init( packageId: UUID("cafecafe-cafe-cafe-cafe-cafecafecafe")!, + packageName: "Alamofire", repositoryOwner: "Alamo", repositoryOwnerName: "Alamofire", repositoryName: "Alamofire", @@ -126,7 +127,7 @@ extension API.PackageController.GetRoute.Model { releaseReference: .tag(5, 2, 0), preReleaseReference: .tag(5, 3, 0, "beta.1"), swift6Readiness: nil, - forkedFromURL: nil + forkedFromResult: nil ) } } From 3cebb099b049e7fc88f434b04c2b886471740b17 Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Tue, 10 Sep 2024 13:18:58 +0200 Subject: [PATCH 05/21] Add icon to css --- FrontEnd/styles/package.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/FrontEnd/styles/package.scss b/FrontEnd/styles/package.scss index 28ee28c6b..51c3a608d 100644 --- a/FrontEnd/styles/package.scss +++ b/FrontEnd/styles/package.scss @@ -69,6 +69,11 @@ display: block; font-size: 11px; } + + li.forked { + grid-column-start: span 2; + background-image: var(--image-fork); + } li.archived { grid-column-start: span 2; From c60f68f21bbf7e75bcd2e1e55499b427e4afdf71 Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Tue, 10 Sep 2024 13:33:46 +0200 Subject: [PATCH 06/21] Fix up tests --- .../API/API+PackageController+GetRoute+Model.swift | 9 ++------- Sources/App/Controllers/API/Types+WithExample.swift | 1 - .../API+PackageController+GetRoute+ModelTests.swift | 12 ++++++------ .../API.PackageController.GetRoute.Model+mock.swift | 1 - 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift b/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift index 0662bd93a..7e0cde201 100644 --- a/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift +++ b/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift @@ -20,7 +20,6 @@ import Vapor extension API.PackageController.GetRoute { struct Model: Content, Equatable { var packageId: Package.Id - var packageName: String var repositoryOwner: String var repositoryOwnerName: String var repositoryName: String @@ -54,7 +53,6 @@ extension API.PackageController.GetRoute { var forkedFromInfo: ForkedFromInfo? internal init(packageId: Package.Id, - packageName: String, repositoryOwner: String, repositoryOwnerName: String, repositoryName: String, @@ -88,7 +86,6 @@ extension API.PackageController.GetRoute { forkedFromResult: API.PackageController.ForkedFromResult? ) { self.packageId = packageId - self.packageName = packageName self.repositoryOwner = repositoryOwner self.repositoryOwnerName = repositoryOwnerName self.repositoryName = repositoryName @@ -132,7 +129,7 @@ extension API.PackageController.GetRoute { switch forkedFromResult { case .fromSPI(let repo, let owner, let ownerName, let packageName): self.forkedFromInfo = ForkedFromInfo.fromSPI( - packageName: packageName, + packageName: self.title, originalOwner: owner, originalOwnerName: ownerName, originalRepo: repo, @@ -161,13 +158,11 @@ extension API.PackageController.GetRoute { let repositoryOwner = repository.owner, let repositoryOwnerName = repository.ownerDisplayName, let repositoryName = repository.name, - let packageId = result.package.id, - let packageName = result.defaultBranchVersion.packageName + let packageId = result.package.id else { return nil } self.init( packageId: packageId, - packageName: packageName, repositoryOwner: repositoryOwner, repositoryOwnerName: repositoryOwnerName, repositoryName: repositoryName, diff --git a/Sources/App/Controllers/API/Types+WithExample.swift b/Sources/App/Controllers/API/Types+WithExample.swift index 4ca47f9c3..900dacfab 100644 --- a/Sources/App/Controllers/API/Types+WithExample.swift +++ b/Sources/App/Controllers/API/Types+WithExample.swift @@ -176,7 +176,6 @@ extension API.PackageController.GetRoute.Model.History: WithExample { extension API.PackageController.GetRoute.Model: WithExample { static var example: Self { .init(packageId: .example, - packageName: "Mona", repositoryOwner: "mona", repositoryOwnerName: "Mona", repositoryName: "LinkedList", diff --git a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift index 69bf96ba0..b9dfb54c1 100644 --- a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift +++ b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift @@ -43,7 +43,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { platformBuildInfo: nil, weightedKeywords: [], swift6Readiness: nil, - forkedFromURL: nil) + forkedFromResult: nil) // validate XCTAssertNotNil(m) @@ -66,7 +66,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { platformBuildInfo: nil, weightedKeywords: [], swift6Readiness: nil, - forkedFromURL: nil + forkedFromResult: nil )) // validate @@ -90,7 +90,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { platformBuildInfo: nil, weightedKeywords: [], swift6Readiness: nil, - forkedFromURL: nil + forkedFromResult: nil )) // validate @@ -118,7 +118,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { platformBuildInfo: nil, weightedKeywords: [], swift6Readiness: nil, - forkedFromURL: nil + forkedFromResult: nil )) // validate @@ -154,7 +154,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { func test_forked_from() throws { var model = API.PackageController.GetRoute.Model.mock - model.forkedFromURL = "https://github.com/owner/repository.git" + model.forkedFromInfo = .fromGitHub(url: "https://github.com/owner/repository.git") let renderedForkedFrom = model.forkedListItem().render(indentedBy: .spaces(2)) assertSnapshot(of: renderedForkedFrom, as: .lines) } @@ -346,7 +346,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { func test_forkedFrom_formatting() throws { var model = API.PackageController.GetRoute.Model.mock - model.forkedFromURL = "https://github.com/owner/repository.git" + model.forkedFromInfo = .fromGitHub(url: "https://github.com/owner/repository.git") let renderedForkedFrom = model.forkedListItem().render() XCTAssertEqual(renderedForkedFrom, "
  • Forked from repository.
  • ") } diff --git a/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift b/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift index 7244fb7b6..2a9fff3bf 100644 --- a/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift +++ b/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift @@ -21,7 +21,6 @@ extension API.PackageController.GetRoute.Model { static var mock: Self { .init( packageId: UUID("cafecafe-cafe-cafe-cafe-cafecafecafe")!, - packageName: "Alamofire", repositoryOwner: "Alamo", repositoryOwnerName: "Alamofire", repositoryName: "Alamofire", From 5f360f2b37fc33fab59bdb43feac89146b8fa40a Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Tue, 10 Sep 2024 15:01:18 +0200 Subject: [PATCH 07/21] Add more tests --- .../API/API+PackageController+GetRoute.swift | 2 +- .../GetRoute.Model+ext.swift | 2 +- ...ackageController+GetRoute+ModelTests.swift | 132 +++++++++++++++++- .../test_forked_from_github.1.txt | 3 + ...rked_from_spi_different_package_name.1.txt | 3 + ...st_forked_from_spi_same_package_name.1.txt | 3 + 6 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_github.1.txt create mode 100644 Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_different_package_name.1.txt create mode 100644 Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_same_package_name.1.txt diff --git a/Sources/App/Controllers/API/API+PackageController+GetRoute.swift b/Sources/App/Controllers/API/API+PackageController+GetRoute.swift index 02f4520e1..546f2f2df 100644 --- a/Sources/App/Controllers/API/API+PackageController+GetRoute.swift +++ b/Sources/App/Controllers/API/API+PackageController+GetRoute.swift @@ -70,7 +70,7 @@ extension API.PackageController { return (model, schema) } - private static func fetchForkedFromResult(on database: Database, repository: Repository) async throws -> ForkedFromResult? { + static func fetchForkedFromResult(on database: Database, repository: Repository) async throws -> ForkedFromResult? { if let forkedFrom = repository.forkedFrom { switch forkedFrom { case .parentId(let id): diff --git a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift index 665b82ce3..d623ba08b 100644 --- a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift +++ b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift @@ -196,7 +196,7 @@ extension API.PackageController.GetRoute.Model { repoURLNode, .text(".") ) - case .fromSPI(let packageName, let owner, let ownerName, let repo, let originalPackageName): + case .fromSPI(let packageName, _, let ownerName, _, let originalPackageName): let repoURLNode: Node if packageName == originalPackageName { repoURLNode = .a( diff --git a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift index b9dfb54c1..492db0a4c 100644 --- a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift +++ b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift @@ -21,6 +21,7 @@ import SPIManifest class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { typealias PackageResult = PackageController.PackageResult + typealias ForkedFromResult = API.PackageController.ForkedFromResult func test_init_no_packageName() async throws { // Tests behaviour when we're lacking data @@ -124,6 +125,79 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { // validate XCTAssertEqual(model.documentationTarget, .external(url: "https://example.com/package/documentation")) } + + func test_init_forked_from_github() async throws { + let pkg = try await savePackage(on: app.db, "1".url) + let repo = try Repository(package: pkg, forkedFrom: .parentURL("https://github.com/example/repo.git"), name: "bar", owner: "foo") + try await repo.save(on: app.db) + let version = try App.Version(package: pkg, latest: .defaultBranch, packageName: nil, reference: .branch("main")) + try await version.save(on: app.db) + let packageResult = try await PackageResult.query(on: app.db, owner: "foo", repository: "bar") + let forkedFromResult = try await API.PackageController.GetRoute.fetchForkedFromResult(on: app.db, repository: packageResult.repository) + + // MUT + let model = try XCTUnwrap(API.PackageController.GetRoute.Model(result: packageResult, + history: nil, + products: [], + targets: [], + swiftVersionBuildInfo: nil, + platformBuildInfo: nil, + weightedKeywords: [], + swift6Readiness: nil, + forkedFromResult: forkedFromResult + )) + + // validate + XCTAssertEqual(model.forkedFromInfo, API.PackageController.GetRoute.Model.ForkedFromInfo.fromGitHub(url: "https://github.com/example/repo.git")) + } + + func test_init_forked_from_spi() async throws { + let originalPkg = try await savePackage(on: app.db, "https://github.com/original/original") + let originalRepo = try Repository( + package: originalPkg, + name: "original", + owner: "original", + ownerName: "OriginalOwner" + ) + try await originalRepo.save(on: app.db) + let originalVersion = try App.Version(package: originalPkg, latest: .defaultBranch, packageName: "OriginalPkg", reference: .branch("main")) + try await originalVersion.save(on: app.db) + let pkg = try await savePackage(on: app.db, "1".url) + let repo = try Repository( + package: pkg, + forkedFrom: .parentId(originalPkg.requireID()), + name: "bar", + owner: "foo" + ) + try await repo.save(on: app.db) + let version = try App.Version(package: pkg, latest: .defaultBranch, packageName: nil, reference: .branch("main")) + try await version.save(on: app.db) + + let packageResult = try await PackageResult.query(on: app.db, owner: "foo", repository: "bar") + let forkedFromResult = try await API.PackageController.GetRoute.fetchForkedFromResult(on: app.db, repository: packageResult.repository) + + // MUT + let model = try XCTUnwrap(API.PackageController.GetRoute.Model(result: packageResult, + history: nil, + products: [], + targets: [], + swiftVersionBuildInfo: nil, + platformBuildInfo: nil, + weightedKeywords: [], + swift6Readiness: nil, + forkedFromResult: forkedFromResult + )) + + // validate + let info = API.PackageController.GetRoute.Model.ForkedFromInfo.fromSPI( + packageName: "bar", + originalOwner: "original", + originalOwnerName: "OriginalOwner", + originalRepo: "original", + originalPackageName: "OriginalPkg" + ) + XCTAssertEqual(model.forkedFromInfo, info) + } func test_gitHubOwnerUrl() throws { var model = API.PackageController.GetRoute.Model.mock @@ -152,12 +226,38 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { assertSnapshot(of: renderedHistory, as: .lines) } - func test_forked_from() throws { + func test_forked_from_github() throws { var model = API.PackageController.GetRoute.Model.mock model.forkedFromInfo = .fromGitHub(url: "https://github.com/owner/repository.git") let renderedForkedFrom = model.forkedListItem().render(indentedBy: .spaces(2)) assertSnapshot(of: renderedForkedFrom, as: .lines) } + + func test_forked_from_spi_same_package_name() throws { + var model = API.PackageController.GetRoute.Model.mock + model.forkedFromInfo = .fromSPI( + packageName: "Test", + originalOwner: "owner", + originalOwnerName: "OriginalOwner", + originalRepo: "repo", + originalPackageName: "Test" + ) + let renderedForkedFrom = model.forkedListItem().render(indentedBy: .spaces(2)) + assertSnapshot(of: renderedForkedFrom, as: .lines) + } + + func test_forked_from_spi_different_package_name() throws { + var model = API.PackageController.GetRoute.Model.mock + model.forkedFromInfo = .fromSPI( + packageName: "Test", + originalOwner: "owner", + originalOwnerName: "OriginalOwner", + originalRepo: "repo", + originalPackageName: "Different" + ) + let renderedForkedFrom = model.forkedListItem().render(indentedBy: .spaces(2)) + assertSnapshot(of: renderedForkedFrom, as: .lines) + } func test_binary_targets() throws { var model = API.PackageController.GetRoute.Model.mock @@ -344,12 +444,40 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { XCTAssertEqual(model.authorsListItem().render(), "
  • By Author One, Author Two, and more!
  • ") } - func test_forkedFrom_formatting() throws { + func test_forkedFrom_github_formatting() throws { var model = API.PackageController.GetRoute.Model.mock model.forkedFromInfo = .fromGitHub(url: "https://github.com/owner/repository.git") let renderedForkedFrom = model.forkedListItem().render() XCTAssertEqual(renderedForkedFrom, "
  • Forked from repository.
  • ") } + + func test_forkedFrom_spi_same_package_name_formatting() throws { + var model = API.PackageController.GetRoute.Model.mock + model.forkedFromInfo = .fromSPI( + packageName: "Test", + originalOwner: "owner", + originalOwnerName: "OriginalOwner", + originalRepo: "repo", + originalPackageName: "Test" + ) + let url = SiteURL.package(.value("owner"), .value("repo"), nil).absoluteURL() + let renderedForkedFrom = model.forkedListItem().render() + XCTAssertEqual(renderedForkedFrom, "
  • Forked from OriginalOwner.
  • ") + } + + func test_forkedFrom_spi_different_package_name_formatting() throws { + var model = API.PackageController.GetRoute.Model.mock + model.forkedFromInfo = .fromSPI( + packageName: "Test", + originalOwner: "owner", + originalOwnerName: "OriginalOwner", + originalRepo: "repo", + originalPackageName: "Different" + ) + let url = SiteURL.package(.value("owner"), .value("repo"), nil).absoluteURL() + let renderedForkedFrom = model.forkedListItem().render() + XCTAssertEqual(renderedForkedFrom, "
  • Forked from Different by OriginalOwner.
  • ") + } func test_BuildInfo_init() throws { // ensure nil propagation when all versions' values are nil diff --git a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_github.1.txt b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_github.1.txt new file mode 100644 index 000000000..620ce3bbe --- /dev/null +++ b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_github.1.txt @@ -0,0 +1,3 @@ +
  • Forked from + repository. +
  • \ No newline at end of file diff --git a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_different_package_name.1.txt b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_different_package_name.1.txt new file mode 100644 index 000000000..b8cf849d6 --- /dev/null +++ b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_different_package_name.1.txt @@ -0,0 +1,3 @@ +
  • Forked from + Different by OriginalOwner. +
  • \ No newline at end of file diff --git a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_same_package_name.1.txt b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_same_package_name.1.txt new file mode 100644 index 000000000..132409929 --- /dev/null +++ b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_same_package_name.1.txt @@ -0,0 +1,3 @@ +
  • Forked from + OriginalOwner. +
  • \ No newline at end of file From e286caeb33d5d1636b378f6d009d5b20bb4207a7 Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Tue, 10 Sep 2024 15:05:01 +0200 Subject: [PATCH 08/21] Fix lint error --- FrontEnd/styles/package.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/FrontEnd/styles/package.scss b/FrontEnd/styles/package.scss index 51c3a608d..1e44aeb87 100644 --- a/FrontEnd/styles/package.scss +++ b/FrontEnd/styles/package.scss @@ -70,11 +70,6 @@ font-size: 11px; } - li.forked { - grid-column-start: span 2; - background-image: var(--image-fork); - } - li.archived { grid-column-start: span 2; background-image: var(--image-warning); From 358e8cb2880e9b9d1a9efcf264af41b3d3438336 Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Fri, 13 Sep 2024 10:42:22 +0200 Subject: [PATCH 09/21] Resolve PR comments --- FrontEnd/styles/package.scss | 2 +- ...I+PackageController+ForkedFromResult.swift | 12 +++++--- ...API+PackageController+GetRoute+Model.swift | 11 +++++++ .../Core/Query+Support/Joined3+Package.swift | 9 ++---- Sources/App/Models/Repository.swift | 6 ---- .../GetRoute.Model+ext.swift | 30 +++++++++---------- ...ackageController+GetRoute+ModelTests.swift | 8 +++-- .../test_forked_from_github.1.txt | 2 +- ...rked_from_spi_different_package_name.1.txt | 3 +- ...st_forked_from_spi_same_package_name.1.txt | 3 +- 10 files changed, 47 insertions(+), 39 deletions(-) diff --git a/FrontEnd/styles/package.scss b/FrontEnd/styles/package.scss index 1e44aeb87..28ee28c6b 100644 --- a/FrontEnd/styles/package.scss +++ b/FrontEnd/styles/package.scss @@ -69,7 +69,7 @@ display: block; font-size: 11px; } - + li.archived { grid-column-start: span 2; background-image: var(--image-warning); diff --git a/Sources/App/Controllers/API/API+PackageController+ForkedFromResult.swift b/Sources/App/Controllers/API/API+PackageController+ForkedFromResult.swift index 54b980ac1..92a0cfeb0 100644 --- a/Sources/App/Controllers/API/API+PackageController+ForkedFromResult.swift +++ b/Sources/App/Controllers/API/API+PackageController+ForkedFromResult.swift @@ -22,15 +22,19 @@ extension API.PackageController { case fromGitHub(url: String) static func query(on database: Database, packageId: Package.Id) async throws -> ForkedFromResult? { - let model = try await Joined3.query(on: database, packageId: packageId).first() - + let model = try await Joined3 + .query(on: database, packageId: packageId, version: .defaultBranch) + .first() + guard let repoName = model?.repository.name, let ownerName = model?.repository.ownerName, - let owner = model?.repository.owner, - let packageName = model?.version.packageName else { + let owner = model?.repository.owner else { return nil } + // fallback to repo name if packageName is nil + let packageName: String = model?.version.packageName ?? repoName + return ForkedFromResult.fromSPI( repository: repoName, owner: owner, diff --git a/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift b/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift index 7e0cde201..c3f1e4c52 100644 --- a/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift +++ b/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift @@ -387,6 +387,17 @@ extension API.PackageController.GetRoute.Model { return url } } + + + + var ownerURL: String? { + switch self { + case .fromSPI(_, let owner, _, _, _): + return "https://github.com/\(owner)" + case .fromGitHub: + return nil + } + } } } diff --git a/Sources/App/Core/Query+Support/Joined3+Package.swift b/Sources/App/Core/Query+Support/Joined3+Package.swift index e10810b66..9c67e3bac 100644 --- a/Sources/App/Core/Query+Support/Joined3+Package.swift +++ b/Sources/App/Core/Query+Support/Joined3+Package.swift @@ -41,11 +41,8 @@ extension Joined3 where M == Package, R1 == Repository, R2 == Version { .filter(Repository.self, \.$name, .custom("ilike"), repository) } - static func query(on database: Database, packageId: Package.Id) -> JoinedQueryBuilder { - query(on: database, - join: \Package.$id == \Repository.$package.$id, method: .inner, - join: \Version.$package.$id == \Package.$id, method: .inner) - .filter(Version.self, \Version.$latest == .defaultBranch) - .filter(Package.self, \Package.$id == packageId) + static func query(on database: Database, packageId: Package.Id, version: Version.Kind) -> JoinedQueryBuilder { + query(on: database, version: version) + .filter(Package.self, \Package.$id == packageId) } } diff --git a/Sources/App/Models/Repository.swift b/Sources/App/Models/Repository.swift index d318b047d..17460835f 100644 --- a/Sources/App/Models/Repository.swift +++ b/Sources/App/Models/Repository.swift @@ -225,12 +225,6 @@ final class Repository: @unchecked Sendable, Model, Content { .first() ?? Repository(packageId: pkgId) } - static func find(on database: Database, for packageId: Package.Id) async throws -> Repository? { - return try await Repository.query(on: database) - .filter(\.$package.$id == packageId) - .first() - } - } diff --git a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift index d623ba08b..5dd2d9f14 100644 --- a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift +++ b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift @@ -187,33 +187,31 @@ extension API.PackageController.GetRoute.Model { let item: Node = { switch forkedFromInfo { case .fromGitHub(let url): + var text = url.replacingOccurrences(of: "https://github.com/", with: "") + text = text.removingSuffix(".git") let repoURLNode: Node = .a( .href(url), - .text("repository") + .text(text) ) return .group( .text("Forked from "), repoURLNode, .text(".") ) - case .fromSPI(let packageName, _, let ownerName, _, let originalPackageName): - let repoURLNode: Node - if packageName == originalPackageName { - repoURLNode = .a( - .href(forkedFromInfo.url), - .text(ownerName) - ) - - } else { - repoURLNode = .a( - .href(forkedFromInfo.url), - .text("\(originalPackageName) by \(ownerName)") - ) - } - + case .fromSPI(_, _, let ownerName, _, let originalPackageName): + let repoURLNode: Node = .a( + .href(forkedFromInfo.url), + .text("\(originalPackageName)") + ) + let ownerNode: Node = .a( + .href(forkedFromInfo.ownerURL ?? ""), + .text("\(ownerName)") + ) return .group( .text("Forked from "), repoURLNode, + .text(" by "), + ownerNode, .text(".") ) } diff --git a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift index 492db0a4c..5c948482f 100644 --- a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift +++ b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift @@ -448,7 +448,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { var model = API.PackageController.GetRoute.Model.mock model.forkedFromInfo = .fromGitHub(url: "https://github.com/owner/repository.git") let renderedForkedFrom = model.forkedListItem().render() - XCTAssertEqual(renderedForkedFrom, "
  • Forked from repository.
  • ") + XCTAssertEqual(renderedForkedFrom, "
  • Forked from owner/repository.
  • ") } func test_forkedFrom_spi_same_package_name_formatting() throws { @@ -461,8 +461,9 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { originalPackageName: "Test" ) let url = SiteURL.package(.value("owner"), .value("repo"), nil).absoluteURL() + var ownerUrl = model.forkedFromInfo?.ownerURL ?? "" let renderedForkedFrom = model.forkedListItem().render() - XCTAssertEqual(renderedForkedFrom, "
  • Forked from OriginalOwner.
  • ") + XCTAssertEqual(renderedForkedFrom, "
  • Forked from Test by OriginalOwner.
  • ") } func test_forkedFrom_spi_different_package_name_formatting() throws { @@ -475,8 +476,9 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { originalPackageName: "Different" ) let url = SiteURL.package(.value("owner"), .value("repo"), nil).absoluteURL() + var ownerUrl = model.forkedFromInfo?.ownerURL ?? "" let renderedForkedFrom = model.forkedListItem().render() - XCTAssertEqual(renderedForkedFrom, "
  • Forked from Different by OriginalOwner.
  • ") + XCTAssertEqual(renderedForkedFrom, "
  • Forked from Different by OriginalOwner.
  • ") } func test_BuildInfo_init() throws { diff --git a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_github.1.txt b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_github.1.txt index 620ce3bbe..be633d8e1 100644 --- a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_github.1.txt +++ b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_github.1.txt @@ -1,3 +1,3 @@
  • Forked from - repository. + owner/repository.
  • \ No newline at end of file diff --git a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_different_package_name.1.txt b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_different_package_name.1.txt index b8cf849d6..b11725063 100644 --- a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_different_package_name.1.txt +++ b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_different_package_name.1.txt @@ -1,3 +1,4 @@
  • Forked from - Different by OriginalOwner. + Different by + OriginalOwner.
  • \ No newline at end of file diff --git a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_same_package_name.1.txt b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_same_package_name.1.txt index 132409929..826917eb4 100644 --- a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_same_package_name.1.txt +++ b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_same_package_name.1.txt @@ -1,3 +1,4 @@
  • Forked from - OriginalOwner. + Test by + OriginalOwner.
  • \ No newline at end of file From e63f8c49b939af3451da4b8b6488f22f4eb3c58c Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Fri, 13 Sep 2024 11:06:30 +0200 Subject: [PATCH 10/21] Use relative URLs instead of absolute --- .../API/API+PackageController+GetRoute+Model.swift | 7 ++----- .../test_forked_from_spi_different_package_name.1.txt | 4 ++-- .../test_forked_from_spi_same_package_name.1.txt | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift b/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift index c3f1e4c52..4639d1805 100644 --- a/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift +++ b/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift @@ -382,24 +382,21 @@ extension API.PackageController.GetRoute.Model { var url: String { switch self { case .fromSPI(_, let originalOwner, _, let originalRepo, _): - return SiteURL.package(.value(originalOwner), .value(originalRepo), nil).absoluteURL() + return SiteURL.package(.value(originalOwner), .value(originalRepo), nil).relativeURL() case .fromGitHub(let url): return url } } - - var ownerURL: String? { switch self { case .fromSPI(_, let owner, _, _, _): - return "https://github.com/\(owner)" + return SiteURL.author(.value(owner)).relativeURL() case .fromGitHub: return nil } } } - } diff --git a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_different_package_name.1.txt b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_different_package_name.1.txt index b11725063..c97d0da5a 100644 --- a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_different_package_name.1.txt +++ b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_different_package_name.1.txt @@ -1,4 +1,4 @@
  • Forked from - Different by - OriginalOwner. + Different by + OriginalOwner.
  • \ No newline at end of file diff --git a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_same_package_name.1.txt b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_same_package_name.1.txt index 826917eb4..eb55eff80 100644 --- a/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_same_package_name.1.txt +++ b/Tests/AppTests/__Snapshots__/API+PackageController+GetRoute+ModelTests/test_forked_from_spi_same_package_name.1.txt @@ -1,4 +1,4 @@
  • Forked from - Test by - OriginalOwner. + Test by + OriginalOwner.
  • \ No newline at end of file From 53e70ed454ad50cc3bec465ff25bea07afd6c335 Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Fri, 13 Sep 2024 11:28:32 +0200 Subject: [PATCH 11/21] Fix tests --- .../API+PackageController+GetRoute+ModelTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift index 5c948482f..734d253f9 100644 --- a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift +++ b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift @@ -460,8 +460,8 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { originalRepo: "repo", originalPackageName: "Test" ) - let url = SiteURL.package(.value("owner"), .value("repo"), nil).absoluteURL() - var ownerUrl = model.forkedFromInfo?.ownerURL ?? "" + let url = SiteURL.package(.value("owner"), .value("repo"), nil).relativeURL() + let ownerUrl = model.forkedFromInfo?.ownerURL ?? "" let renderedForkedFrom = model.forkedListItem().render() XCTAssertEqual(renderedForkedFrom, "
  • Forked from Test by OriginalOwner.
  • ") } @@ -475,8 +475,8 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { originalRepo: "repo", originalPackageName: "Different" ) - let url = SiteURL.package(.value("owner"), .value("repo"), nil).absoluteURL() - var ownerUrl = model.forkedFromInfo?.ownerURL ?? "" + let url = SiteURL.package(.value("owner"), .value("repo"), nil).relativeURL() + let ownerUrl = model.forkedFromInfo?.ownerURL ?? "" let renderedForkedFrom = model.forkedListItem().render() XCTAssertEqual(renderedForkedFrom, "
  • Forked from Different by OriginalOwner.
  • ") } From a6c7883eb0d86aed427d2ac13d14d174b60a41e6 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Sun, 15 Sep 2024 07:31:47 +0200 Subject: [PATCH 12/21] Merge ForkedFromResult and ForkedFromInfo --- ...I+PackageController+ForkedFromResult.swift | 46 --------- ...API+PackageController+GetRoute+Model.swift | 52 ++-------- .../API/API+PackageController+GetRoute.swift | 50 ++++++---- .../Controllers/API/Types+WithExample.swift | 2 +- .../GetRoute.Model+ext.swift | 23 ++++- ...ackageController+GetRoute+ModelTests.swift | 96 ++++--------------- ...ackageController.GetRoute.Model+mock.swift | 2 +- 7 files changed, 81 insertions(+), 190 deletions(-) delete mode 100644 Sources/App/Controllers/API/API+PackageController+ForkedFromResult.swift diff --git a/Sources/App/Controllers/API/API+PackageController+ForkedFromResult.swift b/Sources/App/Controllers/API/API+PackageController+ForkedFromResult.swift deleted file mode 100644 index 92a0cfeb0..000000000 --- a/Sources/App/Controllers/API/API+PackageController+ForkedFromResult.swift +++ /dev/null @@ -1,46 +0,0 @@ -// 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 -import Vapor - -extension API.PackageController { - enum ForkedFromResult { - case fromSPI(repository: String, owner: String, ownerName: String, packageName: String) - case fromGitHub(url: String) - - static func query(on database: Database, packageId: Package.Id) async throws -> ForkedFromResult? { - let model = try await Joined3 - .query(on: database, packageId: packageId, version: .defaultBranch) - .first() - - guard let repoName = model?.repository.name, - let ownerName = model?.repository.ownerName, - let owner = model?.repository.owner else { - return nil - } - - // fallback to repo name if packageName is nil - let packageName: String = model?.version.packageName ?? repoName - - return ForkedFromResult.fromSPI( - repository: repoName, - owner: owner, - ownerName: ownerName, - packageName: packageName - ) - } - } -} diff --git a/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift b/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift index 4639d1805..f24c59228 100644 --- a/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift +++ b/Sources/App/Controllers/API/API+PackageController+GetRoute+Model.swift @@ -83,7 +83,7 @@ extension API.PackageController.GetRoute { preReleaseReference: App.Reference?, fundingLinks: [FundingLink] = [], swift6Readiness: Swift6Readiness?, - forkedFromResult: API.PackageController.ForkedFromResult? + forkedFromInfo: ForkedFromInfo? ) { self.packageId = packageId self.repositoryOwner = repositoryOwner @@ -125,22 +125,7 @@ extension API.PackageController.GetRoute { }() self.fundingLinks = fundingLinks self.swift6Readiness = swift6Readiness - if let forkedFromResult { - switch forkedFromResult { - case .fromSPI(let repo, let owner, let ownerName, let packageName): - self.forkedFromInfo = ForkedFromInfo.fromSPI( - packageName: self.title, - originalOwner: owner, - originalOwnerName: ownerName, - originalRepo: repo, - originalPackageName: packageName - ) - case .fromGitHub(let url): - self.forkedFromInfo = ForkedFromInfo.fromGitHub(url: url) - } - } else { - self.forkedFromInfo = nil - } + self.forkedFromInfo = forkedFromInfo } init?(result: API.PackageController.PackageResult, @@ -151,7 +136,7 @@ extension API.PackageController.GetRoute { platformBuildInfo: BuildInfo?, weightedKeywords: [WeightedKeyword] = [], swift6Readiness: Swift6Readiness?, - forkedFromResult: API.PackageController.ForkedFromResult?) { + forkedFromInfo: ForkedFromInfo?) { // we consider certain attributes as essential and return nil (raising .notFound) let repository = result.repository guard @@ -197,7 +182,7 @@ extension API.PackageController.GetRoute { preReleaseReference: result.preReleaseVersion?.reference, fundingLinks: result.repository.fundingLinks, swift6Readiness: swift6Readiness, - forkedFromResult: forkedFromResult + forkedFromInfo: forkedFromInfo ) } @@ -370,32 +355,11 @@ extension API.PackageController.GetRoute.Model { } enum ForkedFromInfo: Codable, Equatable { - case fromSPI( - packageName: String, - originalOwner: String, - originalOwnerName: String, - originalRepo: String, - originalPackageName: String - ) + case fromSPI(originalOwner: String, + originalOwnerName: String, + originalRepo: String, + originalPackageName: String) case fromGitHub(url: String) - - var url: String { - switch self { - case .fromSPI(_, let originalOwner, _, let originalRepo, _): - return SiteURL.package(.value(originalOwner), .value(originalRepo), nil).relativeURL() - case .fromGitHub(let url): - return url - } - } - - var ownerURL: String? { - switch self { - case .fromSPI(_, let owner, _, _, _): - return SiteURL.author(.value(owner)).relativeURL() - case .fromGitHub: - return nil - } - } } } diff --git a/Sources/App/Controllers/API/API+PackageController+GetRoute.swift b/Sources/App/Controllers/API/API+PackageController+GetRoute.swift index 546f2f2df..2e52654ce 100644 --- a/Sources/App/Controllers/API/API+PackageController+GetRoute.swift +++ b/Sources/App/Controllers/API/API+PackageController+GetRoute.swift @@ -30,10 +30,6 @@ extension API.PackageController { let packageResult = try await PackageResult.query(on: database, owner: owner, repository: repository) - - let forkedFromResult = try? await self.fetchForkedFromResult(on: database, - repository: packageResult.repository) - async let weightedKeywords = WeightedKeyword.query( on: database, keywords: packageResult.repository.keywords ) @@ -49,6 +45,7 @@ extension API.PackageController { async let buildInfo = API.PackageController.BuildInfo.query(on: database, owner: owner, repository: repository) + async let forkedFromInfo = forkedFromInfo(on: database, fork: packageResult.repository.forkedFrom) guard let model = try await Self.Model( @@ -60,7 +57,7 @@ extension API.PackageController { platformBuildInfo: buildInfo.platform, weightedKeywords: weightedKeywords, swift6Readiness: buildInfo.swift6Readiness, - forkedFromResult: forkedFromResult + forkedFromInfo: forkedFromInfo ), let schema = API.PackageSchema(result: packageResult) else { @@ -69,19 +66,6 @@ extension API.PackageController { return (model, schema) } - - static func fetchForkedFromResult(on database: Database, repository: Repository) async throws -> ForkedFromResult? { - if let forkedFrom = repository.forkedFrom { - switch forkedFrom { - case .parentId(let id): - let info = try await ForkedFromResult.query(on: database, packageId: id) - return info - case .parentURL(let url): - return .fromGitHub(url: url) - } - } - return nil - } } } @@ -102,4 +86,34 @@ extension API.PackageController.GetRoute { beta: links[1], latest: links[2]) } + + static func forkedFromInfo(on database: Database, fork: Fork?) async -> Model.ForkedFromInfo? { + guard let forkedFrom = fork else { return nil } + switch forkedFrom { + case let .parentId(id): + return await Model.ForkedFromInfo.query(on: database, packageId: id) + case let .parentURL(url): + return .fromGitHub(url: url) + } + } +} + + +extension API.PackageController.GetRoute.Model.ForkedFromInfo { + static func query(on database: Database, packageId: Package.Id) async -> Self? { + let model = try? await Joined3 + .query(on: database, packageId: packageId, version: .defaultBranch) + .first() + + guard let repoName = model?.repository.name, + let ownerName = model?.repository.ownerName, + let owner = model?.repository.owner else { + return nil + } + + return .fromSPI(originalOwner: owner, + originalOwnerName: ownerName, + originalRepo: repoName, + originalPackageName: model?.version.packageName ?? repoName) + } } diff --git a/Sources/App/Controllers/API/Types+WithExample.swift b/Sources/App/Controllers/API/Types+WithExample.swift index 900dacfab..a15170f81 100644 --- a/Sources/App/Controllers/API/Types+WithExample.swift +++ b/Sources/App/Controllers/API/Types+WithExample.swift @@ -248,7 +248,7 @@ extension API.PackageController.GetRoute.Model: WithExample { releaseReference: .tag(1, 2, 3, "1.2.3"), preReleaseReference: nil, swift6Readiness: nil, - forkedFromResult: nil) + forkedFromInfo: nil) } } diff --git a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift index 5dd2d9f14..b647d1696 100644 --- a/Sources/App/Views/PackageController/GetRoute.Model+ext.swift +++ b/Sources/App/Views/PackageController/GetRoute.Model+ext.swift @@ -198,7 +198,7 @@ extension API.PackageController.GetRoute.Model { repoURLNode, .text(".") ) - case .fromSPI(_, _, let ownerName, _, let originalPackageName): + case .fromSPI(_, let ownerName, _, let originalPackageName): let repoURLNode: Node = .a( .href(forkedFromInfo.url), .text("\(originalPackageName)") @@ -711,3 +711,24 @@ extension API.PackageController.GetRoute.Model.Swift6Readiness { return lines.joined(separator: "\n") } } + + +extension API.PackageController.GetRoute.Model.ForkedFromInfo { + var url: String { + switch self { + case .fromSPI(let originalOwner, _, let originalRepo, _): + return SiteURL.package(.value(originalOwner), .value(originalRepo), nil).relativeURL() + case .fromGitHub(let url): + return url + } + } + + var ownerURL: String? { + switch self { + case .fromSPI(let owner, _, _, _): + return SiteURL.author(.value(owner)).relativeURL() + case .fromGitHub: + return nil + } + } +} diff --git a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift index 734d253f9..5f9af09a6 100644 --- a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift +++ b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift @@ -21,7 +21,6 @@ import SPIManifest class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { typealias PackageResult = PackageController.PackageResult - typealias ForkedFromResult = API.PackageController.ForkedFromResult func test_init_no_packageName() async throws { // Tests behaviour when we're lacking data @@ -44,7 +43,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { platformBuildInfo: nil, weightedKeywords: [], swift6Readiness: nil, - forkedFromResult: nil) + forkedFromInfo: nil) // validate XCTAssertNotNil(m) @@ -67,8 +66,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { platformBuildInfo: nil, weightedKeywords: [], swift6Readiness: nil, - forkedFromResult: nil - )) + forkedFromInfo: nil)) // validate XCTAssertEqual(model.packageIdentity, "swift-bar") @@ -91,8 +89,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { platformBuildInfo: nil, weightedKeywords: [], swift6Readiness: nil, - forkedFromResult: nil - )) + forkedFromInfo: nil)) // validate XCTAssertEqual(model.documentationTarget, .internal(docVersion: .reference("main"), archive: "archive1")) @@ -119,84 +116,29 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { platformBuildInfo: nil, weightedKeywords: [], swift6Readiness: nil, - forkedFromResult: nil - )) + forkedFromInfo: nil)) // validate XCTAssertEqual(model.documentationTarget, .external(url: "https://example.com/package/documentation")) } - func test_init_forked_from_github() async throws { - let pkg = try await savePackage(on: app.db, "1".url) - let repo = try Repository(package: pkg, forkedFrom: .parentURL("https://github.com/example/repo.git"), name: "bar", owner: "foo") - try await repo.save(on: app.db) - let version = try App.Version(package: pkg, latest: .defaultBranch, packageName: nil, reference: .branch("main")) - try await version.save(on: app.db) - let packageResult = try await PackageResult.query(on: app.db, owner: "foo", repository: "bar") - let forkedFromResult = try await API.PackageController.GetRoute.fetchForkedFromResult(on: app.db, repository: packageResult.repository) - - // MUT - let model = try XCTUnwrap(API.PackageController.GetRoute.Model(result: packageResult, - history: nil, - products: [], - targets: [], - swiftVersionBuildInfo: nil, - platformBuildInfo: nil, - weightedKeywords: [], - swift6Readiness: nil, - forkedFromResult: forkedFromResult - )) - - // validate - XCTAssertEqual(model.forkedFromInfo, API.PackageController.GetRoute.Model.ForkedFromInfo.fromGitHub(url: "https://github.com/example/repo.git")) - } - - func test_init_forked_from_spi() async throws { - let originalPkg = try await savePackage(on: app.db, "https://github.com/original/original") - let originalRepo = try Repository( - package: originalPkg, - name: "original", - owner: "original", - ownerName: "OriginalOwner" - ) - try await originalRepo.save(on: app.db) - let originalVersion = try App.Version(package: originalPkg, latest: .defaultBranch, packageName: "OriginalPkg", reference: .branch("main")) - try await originalVersion.save(on: app.db) - let pkg = try await savePackage(on: app.db, "1".url) - let repo = try Repository( - package: pkg, - forkedFrom: .parentId(originalPkg.requireID()), - name: "bar", - owner: "foo" - ) - try await repo.save(on: app.db) - let version = try App.Version(package: pkg, latest: .defaultBranch, packageName: nil, reference: .branch("main")) - try await version.save(on: app.db) - - let packageResult = try await PackageResult.query(on: app.db, owner: "foo", repository: "bar") - let forkedFromResult = try await API.PackageController.GetRoute.fetchForkedFromResult(on: app.db, repository: packageResult.repository) + func test_ForkedFromInfo_query() async throws { + let originalPkg = try await savePackage(on: app.db, id: .id0, "https://github.com/original/original") + try await Repository(package: originalPkg, + name: "original", + owner: "original", + ownerName: "OriginalOwner").save(on: app.db) + try await App.Version(package: originalPkg, latest: .defaultBranch, packageName: "OriginalPkg", reference: .branch("main")) + .save(on: app.db) // MUT - let model = try XCTUnwrap(API.PackageController.GetRoute.Model(result: packageResult, - history: nil, - products: [], - targets: [], - swiftVersionBuildInfo: nil, - platformBuildInfo: nil, - weightedKeywords: [], - swift6Readiness: nil, - forkedFromResult: forkedFromResult - )) + let forkedFrom = await API.PackageController.GetRoute.Model.ForkedFromInfo.query(on: app.db, packageId: .id0) // validate - let info = API.PackageController.GetRoute.Model.ForkedFromInfo.fromSPI( - packageName: "bar", - originalOwner: "original", - originalOwnerName: "OriginalOwner", - originalRepo: "original", - originalPackageName: "OriginalPkg" - ) - XCTAssertEqual(model.forkedFromInfo, info) + XCTAssertEqual(forkedFrom, .fromSPI(originalOwner: "original", + originalOwnerName: "OriginalOwner", + originalRepo: "original", + originalPackageName: "OriginalPkg")) } func test_gitHubOwnerUrl() throws { @@ -236,7 +178,6 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { func test_forked_from_spi_same_package_name() throws { var model = API.PackageController.GetRoute.Model.mock model.forkedFromInfo = .fromSPI( - packageName: "Test", originalOwner: "owner", originalOwnerName: "OriginalOwner", originalRepo: "repo", @@ -249,7 +190,6 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { func test_forked_from_spi_different_package_name() throws { var model = API.PackageController.GetRoute.Model.mock model.forkedFromInfo = .fromSPI( - packageName: "Test", originalOwner: "owner", originalOwnerName: "OriginalOwner", originalRepo: "repo", @@ -454,7 +394,6 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { func test_forkedFrom_spi_same_package_name_formatting() throws { var model = API.PackageController.GetRoute.Model.mock model.forkedFromInfo = .fromSPI( - packageName: "Test", originalOwner: "owner", originalOwnerName: "OriginalOwner", originalRepo: "repo", @@ -469,7 +408,6 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { func test_forkedFrom_spi_different_package_name_formatting() throws { var model = API.PackageController.GetRoute.Model.mock model.forkedFromInfo = .fromSPI( - packageName: "Test", originalOwner: "owner", originalOwnerName: "OriginalOwner", originalRepo: "repo", diff --git a/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift b/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift index 2a9fff3bf..31da3f38e 100644 --- a/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift +++ b/Tests/AppTests/Mocks/API.PackageController.GetRoute.Model+mock.swift @@ -126,7 +126,7 @@ extension API.PackageController.GetRoute.Model { releaseReference: .tag(5, 2, 0), preReleaseReference: .tag(5, 3, 0, "beta.1"), swift6Readiness: nil, - forkedFromResult: nil + forkedFromInfo: nil ) } } From c2bab323fef8d69a7c431f0762d89764434db493 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 02:18:30 +0000 Subject: [PATCH 13/21] NPM: Bump the npm-dependencies group with 2 updates Bumps the npm-dependencies group with 2 updates: [postcss](https://github.com/postcss/postcss) and [stylelint-scss](https://github.com/stylelint-scss/stylelint-scss). Updates `postcss` from 8.4.45 to 8.4.47 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.45...8.4.47) Updates `stylelint-scss` from 6.5.1 to 6.6.0 - [Release notes](https://github.com/stylelint-scss/stylelint-scss/releases) - [Changelog](https://github.com/stylelint-scss/stylelint-scss/blob/master/CHANGELOG.md) - [Commits](https://github.com/stylelint-scss/stylelint-scss/compare/v6.5.1...v6.6.0) --- updated-dependencies: - dependency-name: postcss dependency-type: direct:development update-type: version-update:semver-patch dependency-group: npm-dependencies - dependency-name: stylelint-scss dependency-type: direct:development update-type: version-update:semver-minor dependency-group: npm-dependencies ... Signed-off-by: dependabot[bot] --- package-lock.json | 36 ++++++++++++++++++------------------ package.json | 4 ++-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49ae6b911..738f7892f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,12 +17,12 @@ "devDependencies": { "esbuild": "^0.23.1", "esbuild-sass-plugin": "^3.3.1", - "postcss": "^8.4.45", + "postcss": "^8.4.47", "prettier": "^3.3.3", "stylelint": "^16.9.0", "stylelint-config-standard-scss": "^13.1.0", "stylelint-order": "^6.0.4", - "stylelint-scss": "^6.5.1" + "stylelint-scss": "^6.6.0" } }, "node_modules/@babel/code-frame": { @@ -1764,9 +1764,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "dev": true }, "node_modules/picomatch": { @@ -1781,9 +1781,9 @@ } }, "node_modules/postcss": { - "version": "8.4.45", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", - "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -1801,8 +1801,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -2185,9 +2185,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -2388,17 +2388,17 @@ } }, "node_modules/stylelint-scss": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.5.1.tgz", - "integrity": "sha512-ZLqdqihm6uDYkrsOeD6YWb+stZI8Wn92kUNDhE4M+g9g1aCnRv0JlOrttFiAJJwaNzpdQgX3YJb5vDQXVuO9Ww==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.6.0.tgz", + "integrity": "sha512-aK2Rdt41Jt9Gv/ClMN5BYhP7xR3IEoRMDKpJkIDI9frZQ6KkxeLishusxs2JtEGZdJWXvPoBOhswNxj3gx8L/g==", "dev": true, "dependencies": { "css-tree": "2.3.1", "is-plain-object": "5.0.0", "known-css-properties": "^0.34.0", "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.4", - "postcss-selector-parser": "^6.1.1", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-selector-parser": "^6.1.2", "postcss-value-parser": "^4.2.0" }, "engines": { diff --git a/package.json b/package.json index fbbb0f08d..7f765811b 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,11 @@ "devDependencies": { "esbuild": "^0.23.1", "esbuild-sass-plugin": "^3.3.1", - "postcss": "^8.4.45", + "postcss": "^8.4.47", "prettier": "^3.3.3", "stylelint": "^16.9.0", "stylelint-config-standard-scss": "^13.1.0", "stylelint-order": "^6.0.4", - "stylelint-scss": "^6.5.1" + "stylelint-scss": "^6.6.0" } } From a5c5bb2bdabaf1efb500827bea557b43c3d4e30e Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Mon, 16 Sep 2024 07:25:02 +0200 Subject: [PATCH 14/21] Remove SwiftUICharts (package removed) --- restfiles/doc-test.restfile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/restfiles/doc-test.restfile b/restfiles/doc-test.restfile index 892965cfb..6b1842d96 100644 --- a/restfiles/doc-test.restfile +++ b/restfiles/doc-test.restfile @@ -415,10 +415,6 @@ requests: url: ${base_url}/mchakravarty/CodeEditorView/documentation validation: status: .regex((2|3)\d\d) - /mecid/SwiftUICharts/documentation: - url: ${base_url}/mecid/SwiftUICharts/documentation - validation: - status: .regex((2|3)\d\d) /mergesort/Boutique/documentation: url: ${base_url}/mergesort/Boutique/documentation validation: From e2d3a69e67bcce49189cf466e0b15c9f39e97523 Mon Sep 17 00:00:00 2001 From: "Sven A. Schmidt" Date: Tue, 17 Sep 2024 15:49:18 +0200 Subject: [PATCH 15/21] Add run 12 results --- Resources/ChartData/rfs6-errors.json | 18 ++++++++++++++++++ Resources/ChartData/rfs6-events.json | 4 ++++ Resources/ChartData/rfs6-packages.json | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/Resources/ChartData/rfs6-errors.json b/Resources/ChartData/rfs6-errors.json index 57522401d..308bd498d 100644 --- a/Resources/ChartData/rfs6-errors.json +++ b/Resources/ChartData/rfs6-errors.json @@ -57,6 +57,12 @@ "toolchainId" : "com.apple.dt.Xcode", "toolchainLabel" : "Swift 6.0 Xcode 16.0 beta 6 (16A5230g)", "value" : 62212 + }, + { + "date" : "2024-09-17", + "toolchainId" : "com.apple.dt.Xcode", + "toolchainLabel" : "Swift 6.0 Xcode 16.0 RC (16A242)", + "value" : 57164 } ] }, @@ -118,6 +124,12 @@ "toolchainId" : "com.apple.dt.Xcode", "toolchainLabel" : "Swift 6.0 Xcode 16.0 beta 6 (16A5230g)", "value" : 1458 + }, + { + "date" : "2024-09-17", + "toolchainId" : "com.apple.dt.Xcode", + "toolchainLabel" : "Swift 6.0 Xcode 16.0 RC (16A242)", + "value" : 1502 } ] }, @@ -179,6 +191,12 @@ "toolchainId" : "com.apple.dt.Xcode", "toolchainLabel" : "Swift 6.0 Xcode 16.0 beta 6 (16A5230g)", "value" : 531 + }, + { + "date" : "2024-09-17", + "toolchainId" : "com.apple.dt.Xcode", + "toolchainLabel" : "Swift 6.0 Xcode 16.0 RC (16A242)", + "value" : 452 } ] } diff --git a/Resources/ChartData/rfs6-events.json b/Resources/ChartData/rfs6-events.json index c940fba10..96075ddde 100644 --- a/Resources/ChartData/rfs6-events.json +++ b/Resources/ChartData/rfs6-events.json @@ -22,5 +22,9 @@ { "date": "2024-08-20", "value": "Xcode 16 beta 6 released" + }, + { + "date": "2024-09-09", + "value": "Xcode 16 RC released" } ] diff --git a/Resources/ChartData/rfs6-packages.json b/Resources/ChartData/rfs6-packages.json index 21e0078bc..1c910f426 100644 --- a/Resources/ChartData/rfs6-packages.json +++ b/Resources/ChartData/rfs6-packages.json @@ -57,6 +57,12 @@ "toolchainId" : "com.apple.dt.Xcode", "toolchainLabel" : "Swift 6.0 Xcode 16.0 beta 6 (16A5230g)", "value" : 1545 + }, + { + "date" : "2024-09-17", + "toolchainId" : "com.apple.dt.Xcode", + "toolchainLabel" : "Swift 6.0 Xcode 16.0 RC (16A242)", + "value" : 1556 } ] }, @@ -118,6 +124,12 @@ "toolchainId" : "com.apple.dt.Xcode", "toolchainLabel" : "Swift 6.0 Xcode 16.0 beta 6 (16A5230g)", "value" : 21 + }, + { + "date" : "2024-09-17", + "toolchainId" : "com.apple.dt.Xcode", + "toolchainLabel" : "Swift 6.0 Xcode 16.0 RC (16A242)", + "value" : 22 } ] }, @@ -179,6 +191,12 @@ "toolchainId" : "com.apple.dt.Xcode", "toolchainLabel" : "Swift 6.0 Xcode 16.0 beta 6 (16A5230g)", "value" : 9 + }, + { + "date" : "2024-09-17", + "toolchainId" : "com.apple.dt.Xcode", + "toolchainLabel" : "Swift 6.0 Xcode 16.0 RC (16A242)", + "value" : 10 } ] } From ee4032d3c4d86accb93d1b33cdd25de77090db61 Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Wed, 18 Sep 2024 11:38:07 +0200 Subject: [PATCH 16/21] Update Fork to containt fallback url --- Sources/App/Commands/Ingest.swift | 2 +- .../080/UpdateRepositoryAddForkedFrom2.swift | 31 +++++++++++++++++++ Sources/App/Models/Repository.swift | 2 +- Sources/App/configure.swift | 3 ++ 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 Sources/App/Migrations/080/UpdateRepositoryAddForkedFrom2.swift diff --git a/Sources/App/Commands/Ingest.swift b/Sources/App/Commands/Ingest.swift index 2278d1337..311915592 100644 --- a/Sources/App/Commands/Ingest.swift +++ b/Sources/App/Commands/Ingest.swift @@ -223,7 +223,7 @@ func getFork(on database: Database, parent: Github.Metadata.Parent?) async -> Fo if let packageId = try? await Package.query(on: database) .filter(\.$url, .custom("ilike"), parentUrl) .first()?.id { - return .parentId(packageId) + return .parentId(id: packageId, fallbackURL: parentUrl) } else { return .parentURL(parentUrl) } diff --git a/Sources/App/Migrations/080/UpdateRepositoryAddForkedFrom2.swift b/Sources/App/Migrations/080/UpdateRepositoryAddForkedFrom2.swift new file mode 100644 index 000000000..2e84c1e82 --- /dev/null +++ b/Sources/App/Migrations/080/UpdateRepositoryAddForkedFrom2.swift @@ -0,0 +1,31 @@ +// 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 UpdateRepositoryAddForkedFrom2: AsyncMigration { + func prepare(on database: Database) async throws { + guard let db = database as? SQLDatabase else { + fatalError("Database must be an SQLDatabase ('as? SQLDatabase' must succeed)") + } + + try await db.raw(#"UPDATE "repositories" SET forked_from = NULL"#).run() + } + + func revert(on database: Database) async throws { + // There's nothing we can do to restore the previous state + } +} diff --git a/Sources/App/Models/Repository.swift b/Sources/App/Models/Repository.swift index 17460835f..1b252f83b 100644 --- a/Sources/App/Models/Repository.swift +++ b/Sources/App/Models/Repository.swift @@ -274,6 +274,6 @@ enum S3Readme: Codable, Equatable { } enum Fork: Codable, Equatable { - case parentId(Package.Id) + case parentId(id: Package.Id, fallbackURL: String) case parentURL(String) } diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 48f48c512..7784b038f 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -337,6 +337,9 @@ public func configure(_ app: Application) async throws -> String { do { // Migration 079 - Add `forked_from` to `repositories` app.migrations.add(UpdateRepositoryAddForkedFrom()) } + do { // Migration 080 - Set`forkded_from` to NULL because of Fork model change in Repository + app.migrations.add(UpdateRepositoryAddForkedFrom2()) + } app.asyncCommands.use(Analyze.Command(), as: "analyze") app.asyncCommands.use(CreateRestfileCommand(), as: "create-restfile") From 3a5172afce866b277fb883b02923a5d75c7d22f6 Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Wed, 18 Sep 2024 11:46:05 +0200 Subject: [PATCH 17/21] Fix tests --- Tests/AppTests/IngestorTests.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/AppTests/IngestorTests.swift b/Tests/AppTests/IngestorTests.swift index 01378c213..0804fe49f 100644 --- a/Tests/AppTests/IngestorTests.swift +++ b/Tests/AppTests/IngestorTests.swift @@ -606,16 +606,16 @@ class IngestorTests: AppTestCase { // test lookup when package is in the index let fork = await getFork(on: app.db, parent: .init(url: "https://github.com/foo/parent.git")) - XCTAssertEqual(fork, .parentId(.id0)) - + XCTAssertEqual(fork, .parentId(id: .id0, fallbackURL: "https://github.com/foo/parent.git")) + // test lookup when package is in the index but with different case in URL let fork2 = await getFork(on: app.db, parent: .init(url: "https://github.com/Foo/Parent.git")) - XCTAssertEqual(fork2, .parentId(.id0)) - + XCTAssertEqual(fork2, .parentId(id: .id0, fallbackURL: "https://github.com/Foo/Parent.git")) + // test whem metadata repo url doesn't have `.git` at end let fork3 = await getFork(on: app.db, parent: .init(url: "https://github.com/Foo/Parent")) - XCTAssertEqual(fork3, .parentId(.id0)) - + XCTAssertEqual(fork3, .parentId(id: .id0, fallbackURL: "https://github.com/Foo/Parent.git")) + // test lookup when package is not in the index let fork4 = await getFork(on: app.db, parent: .init(url: "https://github.com/some/other.git")) XCTAssertEqual(fork4, .parentURL("https://github.com/some/other.git")) From 0b40fced8d77e772ec5af5bdddb5c1406bae2547 Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Wed, 18 Sep 2024 12:15:06 +0200 Subject: [PATCH 18/21] Resolve PR comments --- ...orkedFrom2.swift => UpdateRepositoryResetForkedFrom.swift} | 2 +- Sources/App/configure.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename Sources/App/Migrations/080/{UpdateRepositoryAddForkedFrom2.swift => UpdateRepositoryResetForkedFrom.swift} (95%) diff --git a/Sources/App/Migrations/080/UpdateRepositoryAddForkedFrom2.swift b/Sources/App/Migrations/080/UpdateRepositoryResetForkedFrom.swift similarity index 95% rename from Sources/App/Migrations/080/UpdateRepositoryAddForkedFrom2.swift rename to Sources/App/Migrations/080/UpdateRepositoryResetForkedFrom.swift index 2e84c1e82..1ac0b761e 100644 --- a/Sources/App/Migrations/080/UpdateRepositoryAddForkedFrom2.swift +++ b/Sources/App/Migrations/080/UpdateRepositoryResetForkedFrom.swift @@ -16,7 +16,7 @@ import Fluent import SQLKit -struct UpdateRepositoryAddForkedFrom2: AsyncMigration { +struct UpdateRepositoryResetForkedFrom: AsyncMigration { func prepare(on database: Database) async throws { guard let db = database as? SQLDatabase else { fatalError("Database must be an SQLDatabase ('as? SQLDatabase' must succeed)") diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 7784b038f..28cbd3fb6 100644 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -337,8 +337,8 @@ public func configure(_ app: Application) async throws -> String { do { // Migration 079 - Add `forked_from` to `repositories` app.migrations.add(UpdateRepositoryAddForkedFrom()) } - do { // Migration 080 - Set`forkded_from` to NULL because of Fork model change in Repository - app.migrations.add(UpdateRepositoryAddForkedFrom2()) + do { // Migration 080 - Set`forked_from` to NULL because of Fork model change in Repository + app.migrations.add(UpdateRepositoryResetForkedFrom()) } app.asyncCommands.use(Analyze.Command(), as: "analyze") From 57a0b21387eb35513ae24f31309899c776130a05 Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Wed, 18 Sep 2024 15:33:21 +0200 Subject: [PATCH 19/21] Resort to fallback URL when package can't be found --- .../API/API+PackageController+GetRoute.swift | 8 ++++---- .../API+PackageController+GetRoute+ModelTests.swift | 11 ++++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Sources/App/Controllers/API/API+PackageController+GetRoute.swift b/Sources/App/Controllers/API/API+PackageController+GetRoute.swift index 2e52654ce..782967b6c 100644 --- a/Sources/App/Controllers/API/API+PackageController+GetRoute.swift +++ b/Sources/App/Controllers/API/API+PackageController+GetRoute.swift @@ -90,8 +90,8 @@ extension API.PackageController.GetRoute { static func forkedFromInfo(on database: Database, fork: Fork?) async -> Model.ForkedFromInfo? { guard let forkedFrom = fork else { return nil } switch forkedFrom { - case let .parentId(id): - return await Model.ForkedFromInfo.query(on: database, packageId: id) + case .parentId(let id, let fallbackURL): + return await Model.ForkedFromInfo.query(on: database, packageId: id, fallbackURL: fallbackURL) case let .parentURL(url): return .fromGitHub(url: url) } @@ -100,7 +100,7 @@ extension API.PackageController.GetRoute { extension API.PackageController.GetRoute.Model.ForkedFromInfo { - static func query(on database: Database, packageId: Package.Id) async -> Self? { + static func query(on database: Database, packageId: Package.Id, fallbackURL: String) async -> Self? { let model = try? await Joined3 .query(on: database, packageId: packageId, version: .defaultBranch) .first() @@ -108,7 +108,7 @@ extension API.PackageController.GetRoute.Model.ForkedFromInfo { guard let repoName = model?.repository.name, let ownerName = model?.repository.ownerName, let owner = model?.repository.owner else { - return nil + return .fromGitHub(url: fallbackURL) } return .fromSPI(originalOwner: owner, diff --git a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift index 5f9af09a6..59d208927 100644 --- a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift +++ b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift @@ -132,7 +132,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { .save(on: app.db) // MUT - let forkedFrom = await API.PackageController.GetRoute.Model.ForkedFromInfo.query(on: app.db, packageId: .id0) + let forkedFrom = await API.PackageController.GetRoute.Model.ForkedFromInfo.query(on: app.db, packageId: .id0, fallbackURL: "https://github.com/original/original.git") // validate XCTAssertEqual(forkedFrom, .fromSPI(originalOwner: "original", @@ -141,6 +141,15 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { originalPackageName: "OriginalPkg")) } + func test_ForkedFromInfo_query_fallback() async throws { + // when the package can't be found resort to fallback URL + // MUT + let forkedFrom = await API.PackageController.GetRoute.Model.ForkedFromInfo.query(on: app.db, packageId: .id0, fallbackURL: "https://github.com/original/original.git") + + // validate + XCTAssertEqual(forkedFrom, .fromGitHub(url: "https://github.original/original.git")) + } + func test_gitHubOwnerUrl() throws { var model = API.PackageController.GetRoute.Model.mock model.repositoryOwner = "owner" From 6e064b04c906485f39c134cde0f66b1fa2194412 Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Wed, 18 Sep 2024 15:54:18 +0200 Subject: [PATCH 20/21] Fix typo --- Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift index 59d208927..0cbedaa0e 100644 --- a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift +++ b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift @@ -147,7 +147,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { let forkedFrom = await API.PackageController.GetRoute.Model.ForkedFromInfo.query(on: app.db, packageId: .id0, fallbackURL: "https://github.com/original/original.git") // validate - XCTAssertEqual(forkedFrom, .fromGitHub(url: "https://github.original/original.git")) + XCTAssertEqual(forkedFrom, .fromGitHub(url: "https://github/original/original.git")) } func test_gitHubOwnerUrl() throws { From 5e26cd42dd6b1a45201b6cd57fa2e38a321b0c99 Mon Sep 17 00:00:00 2001 From: Ucanbarlic Date: Wed, 18 Sep 2024 15:54:49 +0200 Subject: [PATCH 21/21] Fix url in test --- Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift index 0cbedaa0e..f5d242a31 100644 --- a/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift +++ b/Tests/AppTests/API+PackageController+GetRoute+ModelTests.swift @@ -147,7 +147,7 @@ class API_PackageController_GetRoute_ModelTests: SnapshotTestCase { let forkedFrom = await API.PackageController.GetRoute.Model.ForkedFromInfo.query(on: app.db, packageId: .id0, fallbackURL: "https://github.com/original/original.git") // validate - XCTAssertEqual(forkedFrom, .fromGitHub(url: "https://github/original/original.git")) + XCTAssertEqual(forkedFrom, .fromGitHub(url: "https://github.com/original/original.git")) } func test_gitHubOwnerUrl() throws {