Skip to content

Commit 70eafbc

Browse files
committed
Add CustomCollections page, link it from package page
1 parent fab1f7e commit 70eafbc

File tree

5 files changed

+216
-3
lines changed

5 files changed

+216
-3
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright Dave Verwer, Sven A. Schmidt, and other contributors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Fluent
16+
import Plot
17+
import Vapor
18+
19+
20+
enum CustomCollectionsController {
21+
22+
static func query(on database: Database, name: String, page: Int, pageSize: Int) async throws -> Page<Joined3<Package, Repository, Version>> {
23+
try await Joined3<Package, Repository, Version>
24+
.query(on: database, version: .defaultBranch)
25+
.join(CustomCollectionPackage.self, on: \Package.$id == \CustomCollectionPackage.$package.$id)
26+
.join(CustomCollection.self, on: \CustomCollection.$id == \CustomCollectionPackage.$customCollection.$id)
27+
.filter(CustomCollection.self, \.$name == name)
28+
.sort(Repository.self, \.$name)
29+
.page(page, size: pageSize)
30+
}
31+
32+
struct Query: Codable {
33+
var page: Int
34+
var pageSize: Int
35+
36+
static let defaultPage = 1
37+
static let defaultPageSize = 20
38+
39+
enum CodingKeys: CodingKey {
40+
case page
41+
case pageSize
42+
}
43+
44+
init(from decoder: Decoder) throws {
45+
let container = try decoder.container(keyedBy: CodingKeys.self)
46+
self.page = try container.decodeIfPresent(Int.self, forKey: CodingKeys.page) ?? Self.defaultPage
47+
self.pageSize = try container.decodeIfPresent(Int.self, forKey: CodingKeys.pageSize) ?? Self.defaultPageSize
48+
}
49+
}
50+
51+
@Sendable
52+
static func show(req: Request) async throws -> HTML {
53+
guard let name = req.parameters.get("name") else {
54+
throw Abort(.notFound)
55+
}
56+
let query = try req.query.decode(Query.self)
57+
let page = try await Self.query(on: req.db, name: name, page: query.page, pageSize: query.pageSize)
58+
59+
guard !page.results.isEmpty else {
60+
throw Abort(.notFound)
61+
}
62+
63+
let packageInfo = page.results.compactMap(PackageInfo.init(package:))
64+
65+
let model = CustomCollectionShow.Model(
66+
name: name,
67+
packages: packageInfo,
68+
page: query.page,
69+
hasMoreResults: page.hasMoreResults
70+
)
71+
72+
return CustomCollectionShow.View(path: req.url.path, model: model).document()
73+
}
74+
75+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright Dave Verwer, Sven A. Schmidt, and other contributors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
extension CustomCollectionShow {
16+
struct Model {
17+
var name: String
18+
var packages: [PackageInfo]
19+
var page: Int
20+
var hasMoreResults: Bool
21+
22+
internal init(name: String,
23+
packages: [PackageInfo],
24+
page: Int,
25+
hasMoreResults: Bool) {
26+
self.name = name
27+
self.packages = packages
28+
self.page = page
29+
self.hasMoreResults = hasMoreResults
30+
}
31+
}
32+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright Dave Verwer, Sven A. Schmidt, and other contributors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Plot
16+
17+
18+
enum CustomCollectionShow {
19+
20+
class View: PublicPage {
21+
22+
let model: Model
23+
24+
init(path: String, model: Model) {
25+
self.model = model
26+
super.init(path: path)
27+
}
28+
29+
override func pageTitle() -> String? {
30+
"Packages for collection \(model.name)"
31+
}
32+
33+
override func pageDescription() -> String? {
34+
let packagesClause = model.packages.count > 1 ? "\(model.packages.count) packages" : "1 package"
35+
return "The Swift Package Index is indexing \(packagesClause) for collection \(model.name)."
36+
}
37+
38+
override func breadcrumbs() -> [Breadcrumb] {
39+
[
40+
Breadcrumb(title: "Home", url: SiteURL.home.relativeURL()),
41+
Breadcrumb(title: model.name)
42+
]
43+
}
44+
45+
override func content() -> Node<HTML.BodyContext> {
46+
.group(
47+
.h2(
48+
.class("trimmed"),
49+
.text("Packages for collection “\(model.name)")
50+
),
51+
.ul(
52+
.id("package-list"),
53+
.group(
54+
model.packages.map { .packageListItem(linkUrl: $0.url, packageName: $0.title, summary: $0.description, repositoryOwner: $0.repositoryOwner, repositoryName: $0.repositoryName, stars: $0.stars, lastActivityAt: $0.lastActivityAt, hasDocs: $0.hasDocs ?? false) }
55+
)
56+
),
57+
.if(model.page == 1 && !model.hasMoreResults,
58+
.p(
59+
.strong("\(model.packages.count) \("package".pluralized(for: model.packages.count)).")
60+
)
61+
),
62+
.ul(
63+
.class("pagination"),
64+
.if(model.page > 1, .previousPage(model: model)),
65+
.if(model.hasMoreResults, .nextPage(model: model))
66+
)
67+
)
68+
}
69+
}
70+
71+
}
72+
73+
74+
fileprivate extension Node where Context == HTML.ListContext {
75+
static func previousPage(model: CustomCollectionShow.Model) -> Node<HTML.ListContext> {
76+
let parameters = [
77+
QueryParameter(key: "page", value: model.page - 1)
78+
]
79+
return .li(
80+
.class("previous"),
81+
.a(
82+
.href(SiteURL.collections(.value(model.name))
83+
.relativeURL(parameters: parameters)),
84+
"Previous Page"
85+
)
86+
)
87+
}
88+
89+
static func nextPage(model: CustomCollectionShow.Model) -> Node<HTML.ListContext> {
90+
let parameters = [
91+
QueryParameter(key: "page", value: model.page + 1)
92+
]
93+
return .li(
94+
.class("next"),
95+
.a(
96+
.href(SiteURL.collections(.value(model.name))
97+
.relativeURL(parameters: parameters)),
98+
"Next Page"
99+
)
100+
)
101+
}
102+
}

Sources/App/Views/PackageController/GetRoute.Model+ext.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ extension API.PackageController.GetRoute.Model {
404404
.class("custom-collections"),
405405
.forEach(customCollections, { collection in
406406
.a(
407-
.href(collection.url), // FIXME: link to custom collection page
407+
.href(SiteURL.collections(.value(collection.name)).relativeURL()),
408408
.text("\(collection.name)")
409409
)
410410
})

Sources/App/routes.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,15 @@ func routes(_ app: Application) throws {
107107
app.get(SiteURL.buildMonitor.pathComponents, use: BuildMonitorController.index).excludeFromOpenAPI()
108108
}
109109

110-
do { // build details page
110+
do { // Build details page
111111
app.get(SiteURL.builds(.key).pathComponents, use: BuildController.show).excludeFromOpenAPI()
112112
}
113113

114-
do { // search page
114+
do { // Custom collections page
115+
app.get(SiteURL.collections(.key).pathComponents, use: CustomCollectionsController.show).excludeFromOpenAPI()
116+
}
117+
118+
do { // Search page
115119
app.get(SiteURL.search.pathComponents, use: SearchController.show).excludeFromOpenAPI()
116120
}
117121

0 commit comments

Comments
 (0)