import Foundation struct Bookmark: Codable, Identifiable, Sendable, Hashable { let id: Int var url: String var title: String var description: String var tagNames: [String] var dateAdded: Date var dateModified: Date var isArchived: Bool var unread: Bool var shared: Bool var websiteTitle: String? var websiteDescription: String? var faviconUrl: String? var previewImageUrl: String? // AI-enriched fields stored locally var aiSummary: String? var aiTags: [String]? enum CodingKeys: String, CodingKey { case id, url, title, description, unread, shared case tagNames = "tag_names" case dateAdded = "date_added" case dateModified = "date_modified" case isArchived = "is_archived" case websiteTitle = "website_title" case websiteDescription = "website_description" case faviconUrl = "favicon_url" case previewImageUrl = "preview_image_url" } var displayTitle: String { if !title.isEmpty { return title } if let t = websiteTitle, !t.isEmpty { return t } return url } var domain: String { URL(string: url)?.host ?? url } /// A short content snippet for list previews: the page's scraped meta /// description, falling back to the user's own note. nil when neither exists. var contentExcerpt: String? { for candidate in [websiteDescription, description] { if let c = candidate?.trimmingCharacters(in: .whitespacesAndNewlines), !c.isEmpty { return c } } return nil } } struct BookmarkResponse: Codable, Sendable { let count: Int let next: String? let previous: String? let results: [Bookmark] } /// Response of linkding's `GET /api/bookmarks/check/?url=` — tells us whether the /// URL is already saved, plus scraped metadata and suggested tags for new saves. struct BookmarkCheck: Codable, Sendable { let bookmark: Bookmark? let metadata: Metadata? let autoTags: [String]? struct Metadata: Codable, Sendable { let title: String? let description: String? } enum CodingKeys: String, CodingKey { case bookmark, metadata case autoTags = "auto_tags" } } struct BookmarkCreate: Codable, Sendable { let url: String let title: String let description: String let tagNames: [String] let isArchived: Bool let unread: Bool let shared: Bool enum CodingKeys: String, CodingKey { case url, title, description, unread, shared case tagNames = "tag_names" case isArchived = "is_archived" } init( url: String, title: String, description: String = "", tagNames: [String], isArchived: Bool, unread: Bool, shared: Bool ) { self.url = url self.title = title self.description = description self.tagNames = tagNames self.isArchived = isArchived self.unread = unread self.shared = shared } } struct BookmarkUpdate: Codable, Sendable { var url: String var title: String var description: String var tagNames: [String] var unread: Bool var shared: Bool enum CodingKeys: String, CodingKey { case url, title, description, unread, shared case tagNames = "tag_names" } } struct LinkdingTag: Codable, Sendable { let name: String } struct TagResponse: Codable, Sendable { let results: [LinkdingTag] } struct SmartCollection: Identifiable, Sendable { let id = UUID() let name: String let description: String let bookmarkIds: [Int] }