Implemented phase 6 for the POC prototype in the Website service.

This commit is contained in:
javier committed 2026-09-21 13:53:13 +02:00
1 parent 256d22df40
commit f2f445827b
40 files changed
+1906 -37

No files matched your search

@@ -64,7 +64,11 @@ extension CollectingController: RouterController {
routes.post(.init("\(String.Route.wants)/:id/remove"), use: removeWant)
routes.get(.init(.Route.series), use: seriesIndex)
routes.get(.init("\(String.Route.series)/:slug"), use: series)
routes.post(.init("\(String.Route.series)/:slug/own"), use: toggleOwned)
routes.get(.init(.Route.compare), use: compare)
routes.get(.init(.Route.collection), use: collection)
routes.post(.init(.Route.collection), use: addBooks)
routes.get(.init(.Route.events), use: events)
return routes
}
@@ -240,12 +244,18 @@ private extension CollectingController {
let listings = try await catalogue.listings().filter { $0.seriesSlug == slug }
let issue = request.uri.queryParameters.get("issue", as: Int.self)
let viewer = await sessions.viewer(for: request)
var owned: Set<Int>?
if let user = await sessions.user(for: request) {
owned = try await collecting.ownedIssues(of: user)[slug] ?? []
}
return .init(additionalHeaders: .perViewer) { [analytics, assetVersion] in
SeriesPage(
series: series,
listings: listings,
issue: issue,
owned: owned,
viewer: viewer,
assetVersion: assetVersion,
analytics: analytics
@@ -277,8 +287,114 @@ private extension CollectingController {
}
// MARK: - Collection and events
private extension CollectingController {
/// Handles books added to the collection from its form.
@Sendable
func addBooks(
request: Request,
context: Context
) async throws -> Response {
guard let user = await sessions.user(for: request) else {
return .redirect(to: .Route.signIn(returningTo: .Route.collection), type: .normal)
}
let form = try await URLEncodedFormDecoder().decode(BooksForm.self, from: request, context: context)
try await collecting.own(issues: CollectionPage.issues(in: form.issues), ofSeries: form.series, by: user)
return .redirect(to: .Route.collection, type: .normal)
}
/// Handles a request for the collection tracker.
@Sendable
func collection(
request: Request,
context: Context
) async throws -> Response {
guard let user = await sessions.user(for: request) else {
return .redirect(to: .Route.signIn(returningTo: .Route.collection), type: .found)
}
let catalogue = try await collecting.allSeries()
let owned = try await collecting.ownedIssues(of: user)
let listings = try await self.catalogue.listings()
let viewer = await sessions.viewer(for: request)
return HTMLResponse(additionalHeaders: .perViewer) { [analytics, assetVersion] in
CollectionPage(
catalogue: catalogue,
owned: owned,
listings: listings,
viewer: viewer,
assetVersion: assetVersion,
analytics: analytics
)
}
.response(from: request, context: context)
}
/// Handles a request for the events.
@Sendable
func events(
request: Request,
context: Context
) async throws -> HTMLResponse {
let events = try await collecting.upcomingEvents()
let listings = try await catalogue.listings()
let viewer = await sessions.viewer(for: request)
return .init(additionalHeaders: .perViewer) { [analytics, assetVersion] in
EventsPage(
events: events,
listings: listings,
viewer: viewer,
assetVersion: assetVersion,
analytics: analytics
)
}
}
/// Handles the switch on an issue of a series: into the collection, or out of it.
@Sendable
func toggleOwned(
request: Request,
context: Context
) async throws -> Response {
let slug = try context.parameters.require("slug")
guard let user = await sessions.user(for: request) else {
return .redirect(to: .Route.signIn(returningTo: .Route.series(slug)), type: .normal)
}
let form = try await URLEncodedFormDecoder().decode(IssueForm.self, from: request, context: context)
if try await collecting.ownedIssues(of: user)[slug]?.contains(form.issue) == true {
try await collecting.disown(issue: form.issue, ofSeries: slug, by: user)
} else {
try await collecting.own(issues: [form.issue], ofSeries: slug, by: user)
}
return .redirect(to: .Route.series(slug), type: .normal)
}
}
// MARK: - Structs
/// The fields the add-books form posts.
private struct BooksForm: Decodable {
let series: String
let issues: String
}
/// The field an issue's switch posts.
private struct IssueForm: Decodable {
let issue: Int
}
/// The fields the add-a-want form posts.
private struct WantForm: Decodable {
let title: String