summaryrefslogtreecommitdiff
path: root/Hutch/Networking/PasteService.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-03-19 15:18:38 -0500
committerChristian Cleberg <[email protected]>2026-03-19 15:18:38 -0500
commit659c76df9ff3926cb68886c16167808c23bd8f75 (patch)
tree572bef546fcca19ad37bc1aa7cfb153eb2bf8eb7 /Hutch/Networking/PasteService.swift
parented48e72520c51e4635cd3840dbc569bdd74c950f (diff)
parent6ba4e967d5dfb5d3c7bb97a0f2662f3180595563 (diff)
downloadhutch-2.tar.gz
hutch-2.tar.bz2
hutch-2.zip
v2.0: Merge branch 'dev'v2
Diffstat (limited to 'Hutch/Networking/PasteService.swift')
-rw-r--r--Hutch/Networking/PasteService.swift231
1 files changed, 231 insertions, 0 deletions
diff --git a/Hutch/Networking/PasteService.swift b/Hutch/Networking/PasteService.swift
new file mode 100644
index 0000000..b9aa530
--- /dev/null
+++ b/Hutch/Networking/PasteService.swift
@@ -0,0 +1,231 @@
+import Foundation
+
+struct PasteListPage: Decodable, Sendable {
+ let results: [Paste]
+ let cursor: String?
+}
+
+final class PasteService: Sendable {
+ private let client: SRHTClient
+
+ init(client: SRHTClient) {
+ self.client = client
+ }
+
+ private static let listQuery = """
+ query pastes($cursor: Cursor) {
+ pastes(cursor: $cursor) {
+ results {
+ id
+ created
+ visibility
+ files {
+ filename
+ hash
+ }
+ user {
+ canonicalName
+ }
+ }
+ cursor
+ }
+ }
+ """
+
+ private static let detailQuery = """
+ query paste($id: String!) {
+ paste(id: $id) {
+ id
+ created
+ visibility
+ files {
+ filename
+ hash
+ contents
+ }
+ user {
+ canonicalName
+ }
+ }
+ }
+ """
+
+ private static let createMutation = """
+ mutation createPaste($files: [Upload!]!, $visibility: Visibility!) {
+ create(files: $files, visibility: $visibility) {
+ id
+ created
+ visibility
+ files {
+ filename
+ hash
+ contents
+ }
+ user {
+ canonicalName
+ }
+ }
+ }
+ """
+
+ private static let updateMutation = """
+ mutation updatePaste($id: String!, $visibility: Visibility!) {
+ update(id: $id, visibility: $visibility) {
+ id
+ created
+ visibility
+ files {
+ filename
+ hash
+ contents
+ }
+ user {
+ canonicalName
+ }
+ }
+ }
+ """
+
+ private static let deleteMutation = """
+ mutation deletePaste($id: String!) {
+ delete(id: $id) {
+ id
+ created
+ visibility
+ files {
+ filename
+ hash
+ }
+ user {
+ canonicalName
+ }
+ }
+ }
+ """
+
+ private static let cacheKey = "paste.pastes"
+
+ func listPastes(cursor: String?, useCache: Bool) async throws -> PasteListPage {
+ let variables = cursor.map { ["cursor": $0 as any Sendable] }
+ let result: PasteListResponse
+ if useCache, cursor == nil {
+ result = try await client.executeAndCache(
+ service: .paste,
+ query: Self.listQuery,
+ variables: variables,
+ responseType: PasteListResponse.self,
+ cacheKey: Self.cacheKey
+ )
+ } else {
+ result = try await client.execute(
+ service: .paste,
+ query: Self.listQuery,
+ variables: variables,
+ responseType: PasteListResponse.self
+ )
+ }
+ return result.pastes ?? PasteListPage(results: [], cursor: nil)
+ }
+
+ func loadCachedPastes() -> PasteListPage? {
+ guard let data = client.responseCache.get(forKey: Self.cacheKey) else {
+ return nil
+ }
+
+ let decoder = JSONDecoder()
+ decoder.dateDecodingStrategy = .srhtFlexible
+ guard let response = try? decoder.decode(GraphQLResponse<PasteListResponse>.self, from: data) else {
+ return nil
+ }
+ return response.data?.pastes
+ }
+
+ func loadPaste(id: String) async throws -> Paste? {
+ let result = try await client.execute(
+ service: .paste,
+ query: Self.detailQuery,
+ variables: ["id": id],
+ responseType: PasteDetailResponse.self
+ )
+ return result.paste
+ }
+
+ func createPaste(files: [PasteUploadDraft], visibility: Visibility) async throws -> Paste {
+ let uploadFiles = normalizedUploadFiles(from: files)
+ let variables: [String: any Sendable] = [
+ "files": [String?](repeating: nil, count: uploadFiles.count),
+ "visibility": visibility.rawValue
+ ]
+
+ let result = try await client.executeMultipartFiles(
+ service: .paste,
+ query: Self.createMutation,
+ variables: variables,
+ files: uploadFiles.enumerated().map { index, file in
+ MultipartUploadFile(
+ variablePath: "files.\(index)",
+ fileData: file.data,
+ fileName: file.fileName,
+ mimeType: "text/plain"
+ )
+ },
+ responseType: CreatePasteResponse.self
+ )
+ return result.create
+ }
+
+ func updateVisibility(id: String, visibility: Visibility) async throws -> Paste? {
+ let result = try await client.execute(
+ service: .paste,
+ query: Self.updateMutation,
+ variables: ["id": id, "visibility": visibility.rawValue],
+ responseType: UpdatePasteResponse.self
+ )
+ return result.update
+ }
+
+ func deletePaste(id: String) async throws -> Paste? {
+ let result = try await client.execute(
+ service: .paste,
+ query: Self.deleteMutation,
+ variables: ["id": id],
+ responseType: DeletePasteResponse.self
+ )
+ return result.delete
+ }
+
+ func loadContents(from url: URL) async throws -> String {
+ try await client.fetchText(url: url)
+ }
+
+ private func normalizedUploadFiles(from files: [PasteUploadDraft]) -> [(fileName: String, data: Data)] {
+ files.compactMap { draft in
+ let text = draft.contents
+ guard let data = text.data(using: .utf8) else {
+ return nil
+ }
+
+ return (draft.filename, data)
+ }
+ }
+}
+
+private struct PasteListResponse: Decodable, Sendable {
+ let pastes: PasteListPage?
+}
+
+private struct PasteDetailResponse: Decodable, Sendable {
+ let paste: Paste?
+}
+
+private struct CreatePasteResponse: Decodable, Sendable {
+ let create: Paste
+}
+
+private struct UpdatePasteResponse: Decodable, Sendable {
+ let update: Paste?
+}
+
+private struct DeletePasteResponse: Decodable, Sendable {
+ let delete: Paste?
+}