Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Blackbook.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
3D6714171EE7057CBE848A07 /* BlackbookServerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F49097698C68BE415965A964 /* BlackbookServerApp.swift */; };
3DDF69D8B580022FFDE12FCA /* ServerModelContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB554426E75CDDDB20498E2 /* ServerModelContainer.swift */; };
3F2A2AE985F300274A0AFE5B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 854F37F271CEC66B3F1BE876 /* Assets.xcassets */; };
3F759DE748EA4246FBBBA456 /* DeviceIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535A0087C38A75EF251D28DF /* DeviceIdentity.swift */; };
3F8A392AE676AD3A604B9EC3 /* ModelSyncApply.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56B474D15D62192EC7F6305 /* ModelSyncApply.swift */; };
40C2890B8D20DC8BF540F2B1 /* SubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21EDF5198B759D9C15B32C55 /* SubscriptionView.swift */; };
418089A77A3E793B504C88FB /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CD02AD2B31E4BDFBABB04A9 /* LoginView.swift */; };
Expand Down Expand Up @@ -144,6 +145,7 @@
F363626541A0478C9B36D5FC /* NetworkGraphViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C76007980526C6D096CFBD6 /* NetworkGraphViewModel.swift */; };
F7DD6583F920E520F4EEBE9D /* ClaudeAPIServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA38C3DA0A5DB92E7C98C0F0 /* ClaudeAPIServiceTests.swift */; };
F90B2184862AC79EBA241556 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8222BEE7020B6711480F644E /* Contact.swift */; };
F92545D544AD003815547A34 /* DeviceIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535A0087C38A75EF251D28DF /* DeviceIdentity.swift */; };
F9C8E6C6BBBDE1468720591E /* ContactListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBCBC491C2BFAA0AABA68B3 /* ContactListViewModelTests.swift */; };
FC428EE351F1C7F125A3E819 /* AIInsightsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C58C1200FB5DE674799040E /* AIInsightsView.swift */; };
FC4D35EB1F0F93963E0499E2 /* DateHelpersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B242CFE0D00227BB630A5801 /* DateHelpersTests.swift */; };
Expand Down Expand Up @@ -206,6 +208,7 @@
4DCA116B785721C8942FD325 /* FeatureGatingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureGatingTests.swift; sourceTree = "<group>"; };
4E45E53C6F0C7F7A6808F6EE /* LocalSyncServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalSyncServer.swift; sourceTree = "<group>"; };
51B78E4C7B87A207BBC1A65A /* IMessageSyncService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMessageSyncService.swift; sourceTree = "<group>"; };
535A0087C38A75EF251D28DF /* DeviceIdentity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceIdentity.swift; sourceTree = "<group>"; };
56EBAC598F8585225B1F09E4 /* RelationshipScoreEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelationshipScoreEngine.swift; sourceTree = "<group>"; };
59D5A4C8F3FE7AFE76757690 /* Group.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Group.swift; sourceTree = "<group>"; };
5AA17CB7865FCC549E3A695B /* InteractionViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -637,6 +640,7 @@
FA9E2FA5C2EA9DC5CC7C31F9 /* Constants.swift */,
FAE3F1034482B48A2B7CCF08 /* ContactSyncApply.swift */,
815CE74C56D359F1D0990254 /* DateHelpers.swift */,
535A0087C38A75EF251D28DF /* DeviceIdentity.swift */,
C0BF1D07C08681789AF0CC48 /* FeatureGating.swift */,
2877312225BA97E41E36D583 /* KeychainService.swift */,
61B8831D66944BBBD80A6E6E /* LocalSyncProtocol.swift */,
Expand Down Expand Up @@ -806,6 +810,7 @@
627F00BB1C55B4CD93AD362C /* DashboardView.swift in Sources */,
771CFACFB207D0ED960C9E33 /* DashboardViewModel.swift in Sources */,
6EBB34A3DDBE871B6B3BF70E /* DateHelpers.swift in Sources */,
F92545D544AD003815547A34 /* DeviceIdentity.swift in Sources */,
03915555188215A47C2FFBAF /* DuplicateScanView.swift in Sources */,
BF7F11D06ADAF53524CB7C27 /* EntityListRow.swift in Sources */,
1D5E32E9EE0B56EA56D2D7EC /* FeatureGating.swift in Sources */,
Expand Down Expand Up @@ -909,6 +914,7 @@
59FEFF7D727B2CB6F2E62054 /* ContactRelationship.swift in Sources */,
5C59F290C171167D7664BE76 /* ContactSyncApply.swift in Sources */,
25F4C43A32CF59B6B592D404 /* DateHelpers.swift in Sources */,
3F759DE748EA4246FBBBA456 /* DeviceIdentity.swift in Sources */,
1D1FA4DA1805F09735832F43 /* Group.swift in Sources */,
13B68B5D4ABD5BDC2C1AC8B7 /* Interaction.swift in Sources */,
113034766F531D2FF71921B2 /* LocalSyncProtocol.swift in Sources */,
Expand Down
25 changes: 25 additions & 0 deletions Blackbook/Models/Activity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ final class Activity {
var syncStatus: String = SyncStatus.pending.rawValue
var lastSyncedAt: Date?

// MARK: - Source-device provenance

var createdByDeviceId: String?
var createdByPlatform: String?
var createdByDeviceName: String?
var lastEditedByDeviceId: String?
var lastEditedByPlatform: String?
var lastEditedByDeviceName: String?

var color: Color {
Color(hex: colorHex) ?? .accentColor
}
Expand Down Expand Up @@ -50,5 +59,21 @@ final class Activity {
self.updatedAt = Date()
self.contacts = []
self.groups = []
self.createdByDeviceId = DeviceIdentity.installId
self.createdByPlatform = DeviceIdentity.platform
self.createdByDeviceName = DeviceIdentity.deviceName
self.lastEditedByDeviceId = DeviceIdentity.installId
self.lastEditedByPlatform = DeviceIdentity.platform
self.lastEditedByDeviceName = DeviceIdentity.deviceName
}

func markLocallyEdited() {
updatedAt = Date()
if syncStatus != SyncStatus.deleted.rawValue {
syncStatus = SyncStatus.pending.rawValue
}
lastEditedByDeviceId = DeviceIdentity.installId
lastEditedByPlatform = DeviceIdentity.platform
lastEditedByDeviceName = DeviceIdentity.deviceName
}
}
28 changes: 28 additions & 0 deletions Blackbook/Models/Contact.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ final class Contact {
var lastSyncedAt: Date?
var syncVersion: Int = 0

// MARK: - Source-device provenance

var createdByDeviceId: String?
var createdByPlatform: String?
var createdByDeviceName: String?
var lastEditedByDeviceId: String?
var lastEditedByPlatform: String?
var lastEditedByDeviceName: String?

@Relationship(deleteRule: .cascade, inverse: \Interaction.contact)
var interactions: [Interaction]

Expand Down Expand Up @@ -153,6 +162,25 @@ final class Contact {
self.metViaBacklinks = []
self.mergedIntoContact = nil
self.mergedContacts = []
self.createdByDeviceId = DeviceIdentity.installId
self.createdByPlatform = DeviceIdentity.platform
self.createdByDeviceName = DeviceIdentity.deviceName
self.lastEditedByDeviceId = DeviceIdentity.installId
self.lastEditedByPlatform = DeviceIdentity.platform
self.lastEditedByDeviceName = DeviceIdentity.deviceName
}

/// Mark this record as locally edited: bumps `updatedAt`, flips `syncStatus` to `.pending`
/// (unless already `.deleted`), and refreshes the three `lastEditedBy*` fields to the
/// current device. Use everywhere a local user action mutates the record.
func markLocallyEdited() {
updatedAt = Date()
if syncStatus != SyncStatus.deleted.rawValue {
syncStatus = SyncStatus.pending.rawValue
}
lastEditedByDeviceId = DeviceIdentity.installId
lastEditedByPlatform = DeviceIdentity.platform
lastEditedByDeviceName = DeviceIdentity.deviceName
}
}

Expand Down
25 changes: 25 additions & 0 deletions Blackbook/Models/ContactRelationship.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ final class ContactRelationship {
var syncStatus: String = SyncStatus.pending.rawValue
var lastSyncedAt: Date?

// MARK: - Source-device provenance

var createdByDeviceId: String?
var createdByPlatform: String?
var createdByDeviceName: String?
var lastEditedByDeviceId: String?
var lastEditedByPlatform: String?
var lastEditedByDeviceName: String?

init(
from: Contact,
to: Contact,
Expand All @@ -25,5 +34,21 @@ final class ContactRelationship {
self.label = label
self.strength = strength
self.updatedAt = Date()
self.createdByDeviceId = DeviceIdentity.installId
self.createdByPlatform = DeviceIdentity.platform
self.createdByDeviceName = DeviceIdentity.deviceName
self.lastEditedByDeviceId = DeviceIdentity.installId
self.lastEditedByPlatform = DeviceIdentity.platform
self.lastEditedByDeviceName = DeviceIdentity.deviceName
}

func markLocallyEdited() {
updatedAt = Date()
if syncStatus != SyncStatus.deleted.rawValue {
syncStatus = SyncStatus.pending.rawValue
}
lastEditedByDeviceId = DeviceIdentity.installId
lastEditedByPlatform = DeviceIdentity.platform
lastEditedByDeviceName = DeviceIdentity.deviceName
}
}
25 changes: 25 additions & 0 deletions Blackbook/Models/Group.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ final class Group {
var syncStatus: String = SyncStatus.pending.rawValue
var lastSyncedAt: Date?

// MARK: - Source-device provenance

var createdByDeviceId: String?
var createdByPlatform: String?
var createdByDeviceName: String?
var lastEditedByDeviceId: String?
var lastEditedByPlatform: String?
var lastEditedByDeviceName: String?

var color: Color {
Color(hex: colorHex) ?? .accentColor
}
Expand All @@ -29,5 +38,21 @@ final class Group {
self.contacts = []
self.activities = []
self.updatedAt = Date()
self.createdByDeviceId = DeviceIdentity.installId
self.createdByPlatform = DeviceIdentity.platform
self.createdByDeviceName = DeviceIdentity.deviceName
self.lastEditedByDeviceId = DeviceIdentity.installId
self.lastEditedByPlatform = DeviceIdentity.platform
self.lastEditedByDeviceName = DeviceIdentity.deviceName
}

func markLocallyEdited() {
updatedAt = Date()
if syncStatus != SyncStatus.deleted.rawValue {
syncStatus = SyncStatus.pending.rawValue
}
lastEditedByDeviceId = DeviceIdentity.installId
lastEditedByPlatform = DeviceIdentity.platform
lastEditedByDeviceName = DeviceIdentity.deviceName
}
}
25 changes: 25 additions & 0 deletions Blackbook/Models/Interaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ final class Interaction {
var syncStatus: String = SyncStatus.pending.rawValue
var lastSyncedAt: Date?

// MARK: - Source-device provenance

var createdByDeviceId: String?
var createdByPlatform: String?
var createdByDeviceName: String?
var lastEditedByDeviceId: String?
var lastEditedByPlatform: String?
var lastEditedByDeviceName: String?

/// Creates a new interaction linked to the given contact.
init(
contact: Contact,
Expand All @@ -44,6 +53,22 @@ final class Interaction {
self.sentiment = sentiment
self.createdAt = Date()
self.updatedAt = Date()
self.createdByDeviceId = DeviceIdentity.installId
self.createdByPlatform = DeviceIdentity.platform
self.createdByDeviceName = DeviceIdentity.deviceName
self.lastEditedByDeviceId = DeviceIdentity.installId
self.lastEditedByPlatform = DeviceIdentity.platform
self.lastEditedByDeviceName = DeviceIdentity.deviceName
}

func markLocallyEdited() {
updatedAt = Date()
if syncStatus != SyncStatus.deleted.rawValue {
syncStatus = SyncStatus.pending.rawValue
}
lastEditedByDeviceId = DeviceIdentity.installId
lastEditedByPlatform = DeviceIdentity.platform
lastEditedByDeviceName = DeviceIdentity.deviceName
}
}

Expand Down
25 changes: 25 additions & 0 deletions Blackbook/Models/Location.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ final class Location {
var syncStatus: String = SyncStatus.pending.rawValue
var lastSyncedAt: Date?

// MARK: - Source-device provenance

var createdByDeviceId: String?
var createdByPlatform: String?
var createdByDeviceName: String?
var lastEditedByDeviceId: String?
var lastEditedByPlatform: String?
var lastEditedByDeviceName: String?

var color: Color {
Color(hex: colorHex) ?? .accentColor
}
Expand All @@ -25,5 +34,21 @@ final class Location {
self.icon = icon
self.contacts = []
self.updatedAt = Date()
self.createdByDeviceId = DeviceIdentity.installId
self.createdByPlatform = DeviceIdentity.platform
self.createdByDeviceName = DeviceIdentity.deviceName
self.lastEditedByDeviceId = DeviceIdentity.installId
self.lastEditedByPlatform = DeviceIdentity.platform
self.lastEditedByDeviceName = DeviceIdentity.deviceName
}

func markLocallyEdited() {
updatedAt = Date()
if syncStatus != SyncStatus.deleted.rawValue {
syncStatus = SyncStatus.pending.rawValue
}
lastEditedByDeviceId = DeviceIdentity.installId
lastEditedByPlatform = DeviceIdentity.platform
lastEditedByDeviceName = DeviceIdentity.deviceName
}
}
25 changes: 25 additions & 0 deletions Blackbook/Models/Note.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ final class Note {
var syncStatus: String = SyncStatus.pending.rawValue
var lastSyncedAt: Date?

// MARK: - Source-device provenance

var createdByDeviceId: String?
var createdByPlatform: String?
var createdByDeviceName: String?
var lastEditedByDeviceId: String?
var lastEditedByPlatform: String?
var lastEditedByDeviceName: String?

/// Creates a new note for the given contact, defaulting to the general category.
init(
contact: Contact,
Expand All @@ -26,6 +35,22 @@ final class Note {
self.category = category
self.createdAt = Date()
self.updatedAt = Date()
self.createdByDeviceId = DeviceIdentity.installId
self.createdByPlatform = DeviceIdentity.platform
self.createdByDeviceName = DeviceIdentity.deviceName
self.lastEditedByDeviceId = DeviceIdentity.installId
self.lastEditedByPlatform = DeviceIdentity.platform
self.lastEditedByDeviceName = DeviceIdentity.deviceName
}

func markLocallyEdited() {
updatedAt = Date()
if syncStatus != SyncStatus.deleted.rawValue {
syncStatus = SyncStatus.pending.rawValue
}
lastEditedByDeviceId = DeviceIdentity.installId
lastEditedByPlatform = DeviceIdentity.platform
lastEditedByDeviceName = DeviceIdentity.deviceName
}
}

Expand Down
25 changes: 25 additions & 0 deletions Blackbook/Models/RejectedCalendarEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ final class RejectedCalendarEvent {
var syncStatus: String = SyncStatus.pending.rawValue
var lastSyncedAt: Date?

// MARK: - Source-device provenance

var createdByDeviceId: String?
var createdByPlatform: String?
var createdByDeviceName: String?
var lastEditedByDeviceId: String?
var lastEditedByPlatform: String?
var lastEditedByDeviceName: String?

init(
googleEventId: String,
title: String,
Expand All @@ -27,5 +36,21 @@ final class RejectedCalendarEvent {
self.calendarName = calendarName
self.rejectedAt = Date()
self.updatedAt = Date()
self.createdByDeviceId = DeviceIdentity.installId
self.createdByPlatform = DeviceIdentity.platform
self.createdByDeviceName = DeviceIdentity.deviceName
self.lastEditedByDeviceId = DeviceIdentity.installId
self.lastEditedByPlatform = DeviceIdentity.platform
self.lastEditedByDeviceName = DeviceIdentity.deviceName
}

func markLocallyEdited() {
updatedAt = Date()
if syncStatus != SyncStatus.deleted.rawValue {
syncStatus = SyncStatus.pending.rawValue
}
lastEditedByDeviceId = DeviceIdentity.installId
lastEditedByPlatform = DeviceIdentity.platform
lastEditedByDeviceName = DeviceIdentity.deviceName
}
}
25 changes: 25 additions & 0 deletions Blackbook/Models/Reminder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ final class Reminder {
var syncStatus: String = SyncStatus.pending.rawValue
var lastSyncedAt: Date?

// MARK: - Source-device provenance

var createdByDeviceId: String?
var createdByPlatform: String?
var createdByDeviceName: String?
var lastEditedByDeviceId: String?
var lastEditedByPlatform: String?
var lastEditedByDeviceName: String?

/// True when the reminder is incomplete and its due date has passed.
var isOverdue: Bool {
!isCompleted && dueDate < Date()
Expand All @@ -39,6 +48,22 @@ final class Reminder {
self.isAutoGenerated = isAutoGenerated
self.createdAt = Date()
self.updatedAt = Date()
self.createdByDeviceId = DeviceIdentity.installId
self.createdByPlatform = DeviceIdentity.platform
self.createdByDeviceName = DeviceIdentity.deviceName
self.lastEditedByDeviceId = DeviceIdentity.installId
self.lastEditedByPlatform = DeviceIdentity.platform
self.lastEditedByDeviceName = DeviceIdentity.deviceName
}

func markLocallyEdited() {
updatedAt = Date()
if syncStatus != SyncStatus.deleted.rawValue {
syncStatus = SyncStatus.pending.rawValue
}
lastEditedByDeviceId = DeviceIdentity.installId
lastEditedByPlatform = DeviceIdentity.platform
lastEditedByDeviceName = DeviceIdentity.deviceName
}

/// Computes the next due date based on the recurrence interval, or nil if non-recurring.
Expand Down
Loading
Loading