diff --git a/Packages/FoundationExtensions/Sources/FoundationExtensions/String+Weblog.swift b/Packages/FoundationExtensions/Sources/FoundationExtensions/String+Weblog.swift index a11a349..7593b5c 100644 --- a/Packages/FoundationExtensions/Sources/FoundationExtensions/String+Weblog.swift +++ b/Packages/FoundationExtensions/Sources/FoundationExtensions/String+Weblog.swift @@ -5,25 +5,33 @@ public extension String { /// Creates a weblog entry body with frontmatter from the string content. /// /// This method formats the string as a weblog entry by adding frontmatter - /// with the specified publication date. The resulting format follows the + /// with the specified publication date and status. The resulting format follows the /// OMG.LOL weblog API requirements with ISO 8601 date formatting. /// /// The output format is: /// ``` /// --- /// Date: YYYY-MM-DD HH:MM + /// Status: [status value] /// --- /// /// [string content] /// ``` /// - /// - Parameter date: The publication date to include in the frontmatter + /// - Parameters: + /// - date: The publication date to include in the frontmatter + /// - status: The publication status to include in the frontmatter (e.g., "Draft", "Live", "Feed Only", "Web + /// Only", "Unlisted") /// - Returns: UTF-8 encoded data containing the formatted weblog entry body - func weblogEntryBody(with date: Date) -> Data { + func weblogEntryBody( + date: Date, + status: String + ) -> Data { let formattedString = DateFormatter.iso8601WithShortTime.string(from: date) let body = """ --- Date: \(formattedString) + Status: \(status) --- \(self) diff --git a/Packages/OMGAPI/Sources/OMGAPI/Requests/WeblogRequestFactory.swift b/Packages/OMGAPI/Sources/OMGAPI/Requests/WeblogRequestFactory.swift index abbc30a..b32a128 100644 --- a/Packages/OMGAPI/Sources/OMGAPI/Requests/WeblogRequestFactory.swift +++ b/Packages/OMGAPI/Sources/OMGAPI/Requests/WeblogRequestFactory.swift @@ -83,13 +83,14 @@ public enum WeblogRequestFactory { /// Creates a request to create a new weblog entry. /// /// This method builds a POST request to create a new weblog entry with the - /// specified content and publication date. The request formats the content - /// with frontmatter containing the date and sends it as raw data to the API. + /// specified content, publication date, and status. The request formats the content + /// with frontmatter containing the date and status, then sends it as raw data to the API. /// /// The content is formatted as: /// ``` /// --- /// Date: YYYY-MM-DD HH:MM + /// Status: [status value] /// --- /// /// [content goes here] @@ -97,19 +98,25 @@ public enum WeblogRequestFactory { /// /// This request requires authentication as it creates content on behalf /// of the authenticated user. The API will generate a unique entry ID - /// and URL slug based on the content title. + /// and URL slug based on the content title. The entry's visibility and + /// distribution will be controlled by the specified status. /// /// - Parameters: /// - address: The user address (username) to create the entry for /// - content: The markdown content body of the weblog entry + /// - status: The publication status of the entry (e.g., "Draft", "Live", "Feed Only", "Web Only", "Unlisted") /// - date: The publication date for the entry /// - Returns: A configured network request for creating a weblog entry public static func makeCreateWeblogEntryRequest( address: String, content: String, + status: String, date: Date ) -> NetworkRequest { - let body = content.weblogEntryBody(with: date) + let body = content.weblogEntryBody( + date: date, + status: status + ) return .init( path: "/address/\(address)/weblog/entry", @@ -122,12 +129,13 @@ public enum WeblogRequestFactory { /// /// This method builds a POST request to update an existing weblog entry /// identified by its entry ID. The request formats the updated content - /// with frontmatter containing the new date and sends it as raw data. + /// with frontmatter containing the new date and status, then sends it as raw data. /// /// The content is formatted as: /// ``` /// --- /// Date: YYYY-MM-DD HH:MM + /// Status: [status value] /// --- /// /// [updated content goes here] @@ -135,21 +143,28 @@ public enum WeblogRequestFactory { /// /// This request requires authentication and the user must own the entry /// being updated. The entry's URL and slug will remain unchanged, but - /// the content and metadata will be updated with the new values. + /// the content, publication date, status, and metadata will be updated + /// with the new values. /// /// - Parameters: /// - address: The user address (username) who owns the entry /// - entryID: The unique identifier of the entry to update /// - content: The updated markdown content body of the weblog entry + /// - status: The updated publication status of the entry (e.g., "Draft", "Live", "Feed Only", "Web Only", + /// "Unlisted") /// - date: The updated publication date for the entry /// - Returns: A configured network request for updating the weblog entry public static func makeUpdateWeblogEntryRequest( address: String, entryID: String, content: String, + status: String, date: Date ) -> NetworkRequest { - let body = content.weblogEntryBody(with: date) + let body = content.weblogEntryBody( + date: date, + status: status + ) return .init( path: "/address/\(address)/weblog/entry/\(entryID)", diff --git a/Packages/Weblog/Sources/Weblog/Fixtures/WeblogNetworkServiceMother.swift b/Packages/Weblog/Sources/Weblog/Fixtures/WeblogNetworkServiceMother.swift index f4db3ca..c1f6be6 100644 --- a/Packages/Weblog/Sources/Weblog/Fixtures/WeblogNetworkServiceMother.swift +++ b/Packages/Weblog/Sources/Weblog/Fixtures/WeblogNetworkServiceMother.swift @@ -20,7 +20,12 @@ EntryResponseMother.makeEntryResponses(count: 2) } - func createWeblogEntry(address: String, content: String, date: Date) async throws -> EntryResponse { + func createWeblogEntry( + address: String, + content: String, + status: String, + date: Date + ) async throws -> EntryResponse { EntryResponseMother.makeEntryResponse() } @@ -28,6 +33,7 @@ address: String, entryID: String, content: String, + status: String, date: Date ) async throws -> EntryResponse { EntryResponseMother.makeEntryResponse() diff --git a/Packages/Weblog/Sources/Weblog/Fixtures/WeblogRepositoryMother.swift b/Packages/Weblog/Sources/Weblog/Fixtures/WeblogRepositoryMother.swift index 46cad1c..af0969c 100644 --- a/Packages/Weblog/Sources/Weblog/Fixtures/WeblogRepositoryMother.swift +++ b/Packages/Weblog/Sources/Weblog/Fixtures/WeblogRepositoryMother.swift @@ -39,7 +39,13 @@ // MARK: - Public func fetchEntries() async throws {} - func createOrUpdateEntry(address: String, entryID: String?, body: String, date: Date) async throws {} + func createOrUpdateEntry( + address: String, + entryID: String?, + body: String, + status: String, + date: Date + ) async throws {} func deleteEntry(address: String, entryID: String) async throws {} } diff --git a/Packages/Weblog/Sources/Weblog/Views/Editor/EditorView.swift b/Packages/Weblog/Sources/Weblog/Views/Editor/EditorView.swift index 6d3c635..6a9eda7 100644 --- a/Packages/Weblog/Sources/Weblog/Views/Editor/EditorView.swift +++ b/Packages/Weblog/Sources/Weblog/Views/Editor/EditorView.swift @@ -76,7 +76,7 @@ struct EditorView: View { .gridColumnAlignment(.trailing) Picker( - selection: $viewModel.selectedStatus, + selection: $viewModel.status, content: { ForEach(WeblogEntryStatus.allCases) { status in Text(status.displayName) diff --git a/Packages/Weblog/Sources/Weblog/Views/Editor/EditorViewModel.swift b/Packages/Weblog/Sources/Weblog/Views/Editor/EditorViewModel.swift index 8c7d099..9751b07 100644 --- a/Packages/Weblog/Sources/Weblog/Views/Editor/EditorViewModel.swift +++ b/Packages/Weblog/Sources/Weblog/Views/Editor/EditorViewModel.swift @@ -11,7 +11,7 @@ final class EditorViewModel { var body: String var entryID: String? var date: Date - var selectedStatus: WeblogEntryStatus + var status: WeblogEntryStatus var shouldDismiss = false private(set) var isSubmitting = false @@ -46,7 +46,7 @@ final class EditorViewModel { self.body = body self.date = date self.entryID = entryID - selectedStatus = status + self.status = status self.repository = repository } @@ -63,6 +63,7 @@ final class EditorViewModel { address: address, entryID: entryID, body: body, + status: status.rawValue, date: date ) shouldDismiss = true diff --git a/Packages/Weblog/Sources/WeblogNetworkService/WeblogNetworkService.swift b/Packages/Weblog/Sources/WeblogNetworkService/WeblogNetworkService.swift index e9bce5f..4f3be20 100644 --- a/Packages/Weblog/Sources/WeblogNetworkService/WeblogNetworkService.swift +++ b/Packages/Weblog/Sources/WeblogNetworkService/WeblogNetworkService.swift @@ -41,22 +41,24 @@ public protocol WeblogNetworkServiceProtocol: AnyObject, Sendable { /// Creates a new weblog entry for the specified address. /// /// This method sends a POST request to the OMG.LOL API to create a new weblog entry. - /// The content is formatted with frontmatter including the publication date and - /// sent as raw data to the API endpoint. + /// The content is formatted with frontmatter including the publication date, status, + /// and sent as raw data to the API endpoint. /// /// The request requires authentication and will create a new entry with a /// system-generated unique identifier. The entry will be immediately available - /// at the user's weblog URL. + /// at the user's weblog URL according to the specified status. /// /// - Parameters: /// - address: The user address (username) to create the entry for /// - content: The markdown content body of the weblog entry + /// - status: The publication status of the entry (e.g., "Draft", "Live", "Feed Only", "Web Only", "Unlisted") /// - date: The publication date for the entry /// - Returns: The created weblog entry with metadata and generated ID /// - Throws: Network errors, authentication errors, or API-specific errors. func createWeblogEntry( address: String, content: String, + status: String, date: Date ) async throws -> EntryResponse @@ -64,16 +66,18 @@ public protocol WeblogNetworkServiceProtocol: AnyObject, Sendable { /// /// This method sends a POST request to the OMG.LOL API to update an existing /// weblog entry identified by its entry ID. The content is formatted with - /// frontmatter including the updated publication date and sent as raw data. + /// frontmatter including the updated publication date, status, and sent as raw data. /// /// The request requires authentication and the user must own the entry being /// updated. The entry's URL and slug will remain unchanged, but the content, - /// publication date, and metadata will be updated. + /// publication date, status, and metadata will be updated. /// /// - Parameters: /// - address: The user address (username) who owns the entry /// - entryID: The unique identifier of the entry to update /// - content: The updated markdown content body of the weblog entry + /// - status: The updated publication status of the entry (e.g., "Draft", "Live", "Feed Only", "Web Only", + /// "Unlisted") /// - date: The updated publication date for the entry /// - Returns: The updated weblog entry with new content and metadata /// - Throws: Network errors, authentication errors, or API-specific errors if the entry is not found. @@ -81,6 +85,7 @@ public protocol WeblogNetworkServiceProtocol: AnyObject, Sendable { address: String, entryID: String, content: String, + status: String, date: Date ) async throws -> EntryResponse @@ -143,12 +148,14 @@ actor WeblogNetworkService: WeblogNetworkServiceProtocol { func createWeblogEntry( address: String, content: String, + status: String, date: Date ) async throws -> EntryResponse { let response = try await networkClient.run( WeblogRequestFactory.makeCreateWeblogEntryRequest( address: address, content: content, + status: status, date: date ) ) @@ -163,6 +170,7 @@ actor WeblogNetworkService: WeblogNetworkServiceProtocol { address: String, entryID: String, content: String, + status: String, date: Date ) async throws -> EntryResponse { let response = try await networkClient.run( @@ -170,6 +178,7 @@ actor WeblogNetworkService: WeblogNetworkServiceProtocol { address: address, entryID: entryID, content: content, + status: status, date: date ) ) diff --git a/Packages/Weblog/Sources/WeblogRepository/WeblogRepository.swift b/Packages/Weblog/Sources/WeblogRepository/WeblogRepository.swift index 6563849..9850a7f 100644 --- a/Packages/Weblog/Sources/WeblogRepository/WeblogRepository.swift +++ b/Packages/Weblog/Sources/WeblogRepository/WeblogRepository.swift @@ -60,6 +60,7 @@ public protocol WeblogRepositoryProtocol: Sendable { /// - address: The address for which to create or update the weblog entry. /// - entryID: The unique identifier of the entry to update. If nil, creates a new entry. /// - body: The content body of the weblog entry in markdown format + /// - status: The publication status of the entry (e.g., "Draft", "Live", "Feed Only", "Web Only", "Unlisted") /// - date: The publication date for the entry /// - Throws: Network errors from the remote operation or persistence errors /// from the local storage operations. @@ -67,6 +68,7 @@ public protocol WeblogRepositoryProtocol: Sendable { address: String, entryID: String?, body: String, + status: String, date: Date ) async throws @@ -139,6 +141,7 @@ actor WeblogRepository: WeblogRepositoryProtocol { address: String, entryID: String? = nil, body: String, + status: String, date: Date ) async throws { if let entryID { @@ -146,12 +149,14 @@ actor WeblogRepository: WeblogRepositoryProtocol { address: address, entryID: entryID, body: body, + status: status, date: date ) } else { try await createEntry( address: address, body: body, + status: status, date: date ) } @@ -162,6 +167,7 @@ actor WeblogRepository: WeblogRepositoryProtocol { private func createEntry( address: String, body: String, + status: String, date: Date ) async throws { guard await authSessionService.isLoggedIn else { @@ -171,6 +177,7 @@ actor WeblogRepository: WeblogRepositoryProtocol { _ = try await networkService.createWeblogEntry( address: address, content: body, + status: status, date: date ) @@ -181,6 +188,7 @@ actor WeblogRepository: WeblogRepositoryProtocol { address: String, entryID: String, body: String, + status: String, date: Date ) async throws { guard await authSessionService.isLoggedIn else { @@ -191,6 +199,7 @@ actor WeblogRepository: WeblogRepositoryProtocol { address: address, entryID: entryID, content: body, + status: status, date: date )