Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New routing #1973

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,15 @@
"version": null
}
},
{
"package": "swift-url-routing",
"repositoryURL": "https://github.com/pointfreeco/swift-url-routing",
"state": {
"branch": null,
"revision": "5bf79bb370015e43842a61d558a9ee053171124e",
"version": "0.3.0"
}
},
{
"package": "SwiftPrometheus",
"repositoryURL": "https://github.com/MrLotU/SwiftPrometheus.git",
Expand Down Expand Up @@ -370,6 +379,15 @@
"version": "4.65.0"
}
},
{
"package": "vapor-routing",
"repositoryURL": "https://github.com/pointfreeco/vapor-routing",
"state": {
"branch": null,
"revision": "f07b4d7618bf48b450ed11c9f85b74ba8b9bae6c",
"version": "0.1.1"
}
},
{
"package": "websocket-kit",
"repositoryURL": "https://github.com/vapor/websocket-kit.git",
Expand Down
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ let package = Package(
.package(url: "https://github.com/pointfreeco/swift-parsing.git", from: "0.7.1"),
.package(name: "SnapshotTesting",
url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.7.2"),
.package(url: "https://github.com/pointfreeco/vapor-routing", from: "0.1.0"),
.package(url: "https://github.com/SwiftPackageIndex/SemanticVersion", from: "0.3.0"),
.package(url: "https://github.com/SwiftPackageIndex/SPIManifest", from: "0.7.1"),
.package(url: "https://github.com/handya/OhhAuth.git", from: "1.4.0"),
Expand All @@ -58,6 +59,7 @@ let package = Package(
.product(name: "Parsing", package: "swift-parsing"),
.product(name: "SwiftPMPackageCollections", package: "SwiftPM"),
.product(name: "Vapor", package: "vapor"),
.product(name: "VaporRouting", package: "vapor-routing"),
.target(name: "DependencyResolution"),
]),
.target(name: "DependencyResolution"),
Expand Down
9 changes: 9 additions & 0 deletions Sources/App/Controllers/API/API+SearchController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ import Vapor

extension API {
struct SearchController {
static func get(req: Request, query: String, page: Int) throws -> EventLoopFuture<Search.Response> {
AppMetrics.apiSearchGetTotal?.inc()
return search(database: req.db,
query: query,
page: page,
pageSize: Constants.resultsPageSize)
}

@available(*, deprecated)
static func get(req: Request) throws -> EventLoopFuture<Search.Response> {
let query = req.query[String.self, at: "query"] ?? ""
let page = req.query[Int.self, at: "page"] ?? 1
Expand Down
24 changes: 24 additions & 0 deletions Sources/App/Controllers/KeywordController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum KeywordController {
.page(page, size: pageSize)
}

@available(*, deprecated)
static func show(req: Request) throws -> EventLoopFuture<HTML> {
guard let keyword = req.parameters.get("keyword") else {
return req.eventLoop.future(error: Abort(.notFound))
Expand All @@ -54,4 +55,27 @@ enum KeywordController {
}
}

static func show(req: Request, keyword: String, page: Int) throws -> EventLoopFuture<HTML> {
let pageIndex = page
let pageSize = Constants.resultsPageSize

return Self.query(on: req.db, keyword: keyword, page: pageIndex, pageSize: pageSize)
.flatMapThrowing { page in
guard !page.results.isEmpty else {
throw Abort(.notFound)
}
let packageInfo = page.results
.compactMap(PackageInfo.init(package:))
return KeywordShow.Model(
keyword: keyword,
packages: packageInfo,
page: pageIndex,
hasMoreResults: page.hasMoreResults
)
}
.map {
KeywordShow.View(path: req.url.path, model: $0).document()
}
}

}
17 changes: 17 additions & 0 deletions Sources/App/Controllers/PackageCollectionController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import Vapor


enum PackageCollectionController {

@available(*, deprecated)
static func generate(req: Request) throws -> EventLoopFuture<SignedCollection> {
AppMetrics.packageCollectionGetTotal?.inc()

Expand All @@ -34,4 +36,19 @@ enum PackageCollectionController {
return req.eventLoop.makeFailedFuture($0)
}
}

static func generate(req: Request, owner: String) throws -> EventLoopFuture<SignedCollection> {
AppMetrics.packageCollectionGetTotal?.inc()

return SignedCollection.generate(
db: req.db,
filterBy: .author(owner),
authorName: "\(owner) via the Swift Package Index"
).flatMapError {
if case PackageCollection.Error.noResults = $0 {
return req.eventLoop.makeFailedFuture(Abort(.notFound))
}
return req.eventLoop.makeFailedFuture($0)
}
}
}
13 changes: 2 additions & 11 deletions Sources/App/Controllers/PackageController+routes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,9 @@ import SemanticVersion

enum PackageController {

static func show(req: Request) async throws -> Response {
guard
let owner = req.parameters.get("owner"),
let repository = req.parameters.get("repository")
else {
throw Abort(.notFound)
}

static func show(req: Request, owner: String, repository: String) async throws -> Response {
if repository.lowercased().hasSuffix(".git") {
throw Abort.redirect(to: SiteURL.package(.value(owner),
.value(repository.droppingGitExtension),
.none).absoluteURL(),
throw Abort.redirect(to: SiteRoute.absoluteURL(for: .package(owner: owner, repository: repository)),
type: .permanent)
}

Expand Down
8 changes: 3 additions & 5 deletions Sources/App/Core/RSS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ extension RecentPackage {
}

var rssItem: Node<RSS.ChannelContext> {
let link = SiteURL.package(.value(repositoryOwner),
.value(repositoryName),
.none).absoluteURL()
let link = SiteRoute.absoluteURL(for: .package(owner: repositoryOwner, repository: repositoryName))
return .item(
.guid(.text(rssGuid), .isPermaLink(false)),
.title(packageName),
Expand All @@ -97,8 +95,8 @@ extension RecentRelease {
}

var rssItem: Node<RSS.ChannelContext> {
let packageUrl = SiteURL.package(.value(repositoryOwner), .value(repositoryName), .none).absoluteURL()
let releasesUrl = SiteURL.package(.value(repositoryOwner), .value(repositoryName), .none).absoluteURL(anchor: "releases")
let packageUrl = SiteRoute.absoluteURL(for: .package(owner: repositoryOwner, repository: repositoryName))
let releasesUrl = SiteRoute.absoluteURL(for: .package(owner: repositoryOwner, repository: repositoryName), anchor: "releases")

func layout(_ body: Node<HTML.BodyContext>) -> Node<HTML.BodyContext> {
.div(
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/Core/Search.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ enum Search {
let owner = repositoryOwner,
let name = repositoryName
else { return nil }
return SiteURL.package(.value(owner), .value(name), .none).relativeURL()
return SiteRoute.relativeURL(for: .package(owner: owner, repository: name))
}

var isPackage: Bool {
Expand Down
174 changes: 174 additions & 0 deletions Sources/App/Core/SiteRoute.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright 2020-2021 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 URLRouting
import Vapor


enum SiteRoute {
case api(ApiRoute)
case docs(DocsRoute)
case home
case keywords(keyword: String, page: Int)
case package(owner: String, repository: String, route: PackageRoute = .show)
case packageCollection(owner: String)
case `static`(StaticRoute)
case tryInPlayground(dependencies: String? = nil)

enum StaticRoute: String, CaseIterable {
case addAPackage = "add-a-package"
case faq
case packageCollections = "package-collections"
case privacy
}

static let router = OneOf {
// /api/...
Route(.case(Self.api)) { Path { "api" }; ApiRoute.router }

// /docs/...
Route(.case(Self.docs)) { Path { "docs"; DocsRoute.parser() } }

// GET /
Route(.case(Self.home))

// GET /keywords/:keyword?page=1
Route(.case(Self.keywords(keyword:page:))) {
Path { "keywords" }; Path { Parse(.string) }
Query { Field("page", default: 1) { Int.parser() } }
}

// GET /:owner/collection.json
Route(.case(Self.packageCollection(owner:))) {
Path { Parse(.string) }
Path { "collection.json" }
}

// /:owner/:repository/...
Route(.case(Self.package(owner:repository:route:))) {
Path { Parse(.string) }
Path { Parse(.string) }
PackageRoute.router
}

// GET /add-a-package
// GET /faq
// GET /package-collections
// GET /privacy
Route(.case(Self.static)) { Path { StaticRoute.parser() } }

// GET /try-in-a-playground?dependencies=foo/bar
Route(.case(Self.tryInPlayground(dependencies:))) {
Path { "try-in-a-playground"}
Optionally {
Query {
Field("dependencies")
}
}
}
}

static func handler(req: Request, route: SiteRoute) async throws -> AsyncResponseEncodable {
switch route {
case let .api(.search(query: query, page: page)):
return try await API.SearchController.get(req: req, query: query, page: page).get()

case .api(.version):
return API.Version(version: appVersion ?? "Unknown")

case .docs, .static, .tryInPlayground:
let filename = try router.print(route).path.joined(separator: "/") + ".md"
return MarkdownPage(path: req.url.path, filename).document()

case .home:
return try await HomeIndex.Model.query(database: req.db).map {
HomeIndex.View(path: req.url.path, model: $0).document()
}.get()

case let .keywords(keyword: keyword, page: page):
return try await KeywordController.show(req: req, keyword: keyword, page: page).get()

case let .package(owner: owner, repository: repository, route: packageRoute):
return try await PackageRoute.handler(req: req, owner: owner, repository: repository, route: packageRoute)

case let .packageCollection(owner: owner):
return try await PackageCollectionController.generate(req: req, owner: owner).get()
}
}
}


enum ApiRoute {
case search(query: String, page: Int)
case version

static let router = OneOf {
// GET /search?query="foo"&page=1
Route(.case(Self.search)) {
Path { "search" }
Query {
Field("query", default: "")
Field("page", default: 1) { Int.parser() }
}
}

// GET /version
Route(.case(Self.version)) { Path { "version" } }
}
}


enum DocsRoute: String, CaseIterable {
case builds
}


enum PackageRoute {
case show

static let router = OneOf {
Route(.case(Self.show))
}

static func handler(req: Request, owner: String, repository: String, route: PackageRoute) async throws -> AsyncResponseEncodable {
switch route {
case .show:
return try await PackageController
.show(req: req, owner: owner, repository: repository)
}
}
}


// MARK: - URL printer helpers

extension SiteRoute {
static func absoluteURL(for route: Self) -> String {
router
.baseURL(Current.siteURL())
.url(for: route).absoluteString
}

static func absoluteURL(for route: Self, anchor: String) -> String {
absoluteURL(for: route) + "#\(anchor)"
}

static func relativeURL(for route: Self) -> String {
router.path(for: route)
}

static func relativeURL(for route: Self, anchor: String) -> String {
relativeURL(for: route) + "#\(anchor)"
}
}
Loading