Publish Weblog entry with the specified status

This commit is contained in:
Otavio Cordeiro 2025-12-21 14:53:15 +01:00 committed by Otávio
parent 3365b19b8f
commit d840caa026
8 changed files with 74 additions and 20 deletions

View file

@ -5,25 +5,33 @@ public extension String {
/// Creates a weblog entry body with frontmatter from the string content. /// Creates a weblog entry body with frontmatter from the string content.
/// ///
/// This method formats the string as a weblog entry by adding frontmatter /// 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. /// OMG.LOL weblog API requirements with ISO 8601 date formatting.
/// ///
/// The output format is: /// The output format is:
/// ``` /// ```
/// --- /// ---
/// Date: YYYY-MM-DD HH:MM /// Date: YYYY-MM-DD HH:MM
/// Status: [status value]
/// --- /// ---
/// ///
/// [string content] /// [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 /// - 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 formattedString = DateFormatter.iso8601WithShortTime.string(from: date)
let body = """ let body = """
--- ---
Date: \(formattedString) Date: \(formattedString)
Status: \(status)
--- ---
\(self) \(self)

View file

@ -83,13 +83,14 @@ public enum WeblogRequestFactory {
/// Creates a request to create a new weblog entry. /// Creates a request to create a new weblog entry.
/// ///
/// This method builds a POST request to create a new weblog entry with the /// This method builds a POST request to create a new weblog entry with the
/// specified content and publication date. The request formats the content /// specified content, publication date, and status. The request formats the content
/// with frontmatter containing the date and sends it as raw data to the API. /// with frontmatter containing the date and status, then sends it as raw data to the API.
/// ///
/// The content is formatted as: /// The content is formatted as:
/// ``` /// ```
/// --- /// ---
/// Date: YYYY-MM-DD HH:MM /// Date: YYYY-MM-DD HH:MM
/// Status: [status value]
/// --- /// ---
/// ///
/// [content goes here] /// [content goes here]
@ -97,19 +98,25 @@ public enum WeblogRequestFactory {
/// ///
/// This request requires authentication as it creates content on behalf /// This request requires authentication as it creates content on behalf
/// of the authenticated user. The API will generate a unique entry ID /// 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: /// - Parameters:
/// - address: The user address (username) to create the entry for /// - address: The user address (username) to create the entry for
/// - content: The markdown content body of the weblog entry /// - 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 /// - date: The publication date for the entry
/// - Returns: A configured network request for creating a weblog entry /// - Returns: A configured network request for creating a weblog entry
public static func makeCreateWeblogEntryRequest( public static func makeCreateWeblogEntryRequest(
address: String, address: String,
content: String, content: String,
status: String,
date: Date date: Date
) -> NetworkRequest<Data, CreateOrUpdateWeblogEntryResponse> { ) -> NetworkRequest<Data, CreateOrUpdateWeblogEntryResponse> {
let body = content.weblogEntryBody(with: date) let body = content.weblogEntryBody(
date: date,
status: status
)
return .init( return .init(
path: "/address/\(address)/weblog/entry", path: "/address/\(address)/weblog/entry",
@ -122,12 +129,13 @@ public enum WeblogRequestFactory {
/// ///
/// This method builds a POST request to update an existing weblog entry /// This method builds a POST request to update an existing weblog entry
/// identified by its entry ID. The request formats the updated content /// 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: /// The content is formatted as:
/// ``` /// ```
/// --- /// ---
/// Date: YYYY-MM-DD HH:MM /// Date: YYYY-MM-DD HH:MM
/// Status: [status value]
/// --- /// ---
/// ///
/// [updated content goes here] /// [updated content goes here]
@ -135,21 +143,28 @@ public enum WeblogRequestFactory {
/// ///
/// This request requires authentication and the user must own the entry /// This request requires authentication and the user must own the entry
/// being updated. The entry's URL and slug will remain unchanged, but /// 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: /// - Parameters:
/// - address: The user address (username) who owns the entry /// - address: The user address (username) who owns the entry
/// - entryID: The unique identifier of the entry to update /// - entryID: The unique identifier of the entry to update
/// - content: The updated markdown content body of the weblog entry /// - 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 /// - date: The updated publication date for the entry
/// - Returns: A configured network request for updating the weblog entry /// - Returns: A configured network request for updating the weblog entry
public static func makeUpdateWeblogEntryRequest( public static func makeUpdateWeblogEntryRequest(
address: String, address: String,
entryID: String, entryID: String,
content: String, content: String,
status: String,
date: Date date: Date
) -> NetworkRequest<Data, CreateOrUpdateWeblogEntryResponse> { ) -> NetworkRequest<Data, CreateOrUpdateWeblogEntryResponse> {
let body = content.weblogEntryBody(with: date) let body = content.weblogEntryBody(
date: date,
status: status
)
return .init( return .init(
path: "/address/\(address)/weblog/entry/\(entryID)", path: "/address/\(address)/weblog/entry/\(entryID)",

View file

@ -20,7 +20,12 @@
EntryResponseMother.makeEntryResponses(count: 2) 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() EntryResponseMother.makeEntryResponse()
} }
@ -28,6 +33,7 @@
address: String, address: String,
entryID: String, entryID: String,
content: String, content: String,
status: String,
date: Date date: Date
) async throws -> EntryResponse { ) async throws -> EntryResponse {
EntryResponseMother.makeEntryResponse() EntryResponseMother.makeEntryResponse()

View file

@ -39,7 +39,13 @@
// MARK: - Public // MARK: - Public
func fetchEntries() async throws {} 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 {} func deleteEntry(address: String, entryID: String) async throws {}
} }

View file

@ -76,7 +76,7 @@ struct EditorView: View {
.gridColumnAlignment(.trailing) .gridColumnAlignment(.trailing)
Picker( Picker(
selection: $viewModel.selectedStatus, selection: $viewModel.status,
content: { content: {
ForEach(WeblogEntryStatus.allCases) { status in ForEach(WeblogEntryStatus.allCases) { status in
Text(status.displayName) Text(status.displayName)

View file

@ -11,7 +11,7 @@ final class EditorViewModel {
var body: String var body: String
var entryID: String? var entryID: String?
var date: Date var date: Date
var selectedStatus: WeblogEntryStatus var status: WeblogEntryStatus
var shouldDismiss = false var shouldDismiss = false
private(set) var isSubmitting = false private(set) var isSubmitting = false
@ -46,7 +46,7 @@ final class EditorViewModel {
self.body = body self.body = body
self.date = date self.date = date
self.entryID = entryID self.entryID = entryID
selectedStatus = status self.status = status
self.repository = repository self.repository = repository
} }
@ -63,6 +63,7 @@ final class EditorViewModel {
address: address, address: address,
entryID: entryID, entryID: entryID,
body: body, body: body,
status: status.rawValue,
date: date date: date
) )
shouldDismiss = true shouldDismiss = true

View file

@ -41,22 +41,24 @@ public protocol WeblogNetworkServiceProtocol: AnyObject, Sendable {
/// Creates a new weblog entry for the specified address. /// 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. /// 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 /// The content is formatted with frontmatter including the publication date, status,
/// sent as raw data to the API endpoint. /// and sent as raw data to the API endpoint.
/// ///
/// The request requires authentication and will create a new entry with a /// The request requires authentication and will create a new entry with a
/// system-generated unique identifier. The entry will be immediately available /// 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: /// - Parameters:
/// - address: The user address (username) to create the entry for /// - address: The user address (username) to create the entry for
/// - content: The markdown content body of the weblog entry /// - 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 /// - date: The publication date for the entry
/// - Returns: The created weblog entry with metadata and generated ID /// - Returns: The created weblog entry with metadata and generated ID
/// - Throws: Network errors, authentication errors, or API-specific errors. /// - Throws: Network errors, authentication errors, or API-specific errors.
func createWeblogEntry( func createWeblogEntry(
address: String, address: String,
content: String, content: String,
status: String,
date: Date date: Date
) async throws -> EntryResponse ) 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 /// 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 /// 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 /// 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, /// 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: /// - Parameters:
/// - address: The user address (username) who owns the entry /// - address: The user address (username) who owns the entry
/// - entryID: The unique identifier of the entry to update /// - entryID: The unique identifier of the entry to update
/// - content: The updated markdown content body of the weblog entry /// - 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 /// - date: The updated publication date for the entry
/// - Returns: The updated weblog entry with new content and metadata /// - 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. /// - 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, address: String,
entryID: String, entryID: String,
content: String, content: String,
status: String,
date: Date date: Date
) async throws -> EntryResponse ) async throws -> EntryResponse
@ -143,12 +148,14 @@ actor WeblogNetworkService: WeblogNetworkServiceProtocol {
func createWeblogEntry( func createWeblogEntry(
address: String, address: String,
content: String, content: String,
status: String,
date: Date date: Date
) async throws -> EntryResponse { ) async throws -> EntryResponse {
let response = try await networkClient.run( let response = try await networkClient.run(
WeblogRequestFactory.makeCreateWeblogEntryRequest( WeblogRequestFactory.makeCreateWeblogEntryRequest(
address: address, address: address,
content: content, content: content,
status: status,
date: date date: date
) )
) )
@ -163,6 +170,7 @@ actor WeblogNetworkService: WeblogNetworkServiceProtocol {
address: String, address: String,
entryID: String, entryID: String,
content: String, content: String,
status: String,
date: Date date: Date
) async throws -> EntryResponse { ) async throws -> EntryResponse {
let response = try await networkClient.run( let response = try await networkClient.run(
@ -170,6 +178,7 @@ actor WeblogNetworkService: WeblogNetworkServiceProtocol {
address: address, address: address,
entryID: entryID, entryID: entryID,
content: content, content: content,
status: status,
date: date date: date
) )
) )

View file

@ -60,6 +60,7 @@ public protocol WeblogRepositoryProtocol: Sendable {
/// - address: The address for which to create or update the weblog entry. /// - 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. /// - 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 /// - 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 /// - date: The publication date for the entry
/// - Throws: Network errors from the remote operation or persistence errors /// - Throws: Network errors from the remote operation or persistence errors
/// from the local storage operations. /// from the local storage operations.
@ -67,6 +68,7 @@ public protocol WeblogRepositoryProtocol: Sendable {
address: String, address: String,
entryID: String?, entryID: String?,
body: String, body: String,
status: String,
date: Date date: Date
) async throws ) async throws
@ -139,6 +141,7 @@ actor WeblogRepository: WeblogRepositoryProtocol {
address: String, address: String,
entryID: String? = nil, entryID: String? = nil,
body: String, body: String,
status: String,
date: Date date: Date
) async throws { ) async throws {
if let entryID { if let entryID {
@ -146,12 +149,14 @@ actor WeblogRepository: WeblogRepositoryProtocol {
address: address, address: address,
entryID: entryID, entryID: entryID,
body: body, body: body,
status: status,
date: date date: date
) )
} else { } else {
try await createEntry( try await createEntry(
address: address, address: address,
body: body, body: body,
status: status,
date: date date: date
) )
} }
@ -162,6 +167,7 @@ actor WeblogRepository: WeblogRepositoryProtocol {
private func createEntry( private func createEntry(
address: String, address: String,
body: String, body: String,
status: String,
date: Date date: Date
) async throws { ) async throws {
guard await authSessionService.isLoggedIn else { guard await authSessionService.isLoggedIn else {
@ -171,6 +177,7 @@ actor WeblogRepository: WeblogRepositoryProtocol {
_ = try await networkService.createWeblogEntry( _ = try await networkService.createWeblogEntry(
address: address, address: address,
content: body, content: body,
status: status,
date: date date: date
) )
@ -181,6 +188,7 @@ actor WeblogRepository: WeblogRepositoryProtocol {
address: String, address: String,
entryID: String, entryID: String,
body: String, body: String,
status: String,
date: Date date: Date
) async throws { ) async throws {
guard await authSessionService.isLoggedIn else { guard await authSessionService.isLoggedIn else {
@ -191,6 +199,7 @@ actor WeblogRepository: WeblogRepositoryProtocol {
address: address, address: address,
entryID: entryID, entryID: entryID,
content: body, content: body,
status: status,
date: date date: date
) )