Sources/KeycaskCore/Entry.swift
65 lines · 2008 bytes
1import Foundation
2
3public struct Entry: Codable, Equatable, Sendable {
4 public let id: EntryID
5 public var name: String
6 public var username: String?
7 public var password: String
8 public var url: String?
9 public var notes: String?
10 public var tags: [String]
11 public let created: Date
12 public var updated: Date
13
14 public init(
15 id: EntryID = .random(),
16 name: String,
17 username: String? = nil,
18 password: String,
19 url: String? = nil,
20 notes: String? = nil,
21 tags: [String] = [],
22 now: Date = .now
23 ) {
24 self.id = id
25 self.name = name
26 self.username = username
27 self.password = password
28 self.url = url
29 self.notes = notes
30 self.tags = Self.normalize(tags: tags)
31 let stamp = Self.truncateToSeconds(now)
32 created = stamp
33 updated = stamp
34 }
35
36 public static func normalize(tags: [String]) -> [String] {
37 var seen: Set<String> = []
38 var out: [String] = []
39 for raw in tags {
40 let tag = raw.trimmingCharacters(in: .whitespaces)
41 guard !tag.isEmpty, seen.insert(tag.lowercased()).inserted else { continue }
42 out.append(tag)
43 }
44 return out.sorted { a, b in
45 let (la, lb) = (a.lowercased(), b.lowercased())
46 return la == lb ? a < b : la < lb
47 }
48 }
49
50 public static func truncateToSeconds(_ date: Date) -> Date {
51 Date(timeIntervalSince1970: date.timeIntervalSince1970.rounded(.down))
52 }
53
54 public func hasTag(_ tag: String) -> Bool {
55 let needle = tag.lowercased()
56 return tags.contains { $0.lowercased() == needle }
57 }
58
59 public func matches(_ query: String) -> Bool {
60 let needle = query.lowercased()
61 guard !needle.isEmpty else { return false }
62 let haystacks = [name, username ?? "", url ?? "", notes ?? ""] + tags
63 return haystacks.contains { $0.lowercased().contains(needle) }
64 }
65}