Skip to content
This repository has been archived by the owner on Aug 11, 2024. It is now read-only.

Log key activities in SQLite #1067

Merged
merged 10 commits into from
Jan 16, 2024
60 changes: 54 additions & 6 deletions xcode/Subconscious/Shared/Components/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@ enum AppAction: Hashable {
case failMergeEntry(parent: Slashlink, child: Slashlink, error: String)
case failUpdateAudience(address: Slashlink, audience: Audience, error: String)

case succeedLogActivity
case failLogActivity(_ error: String)

case setSelectedAppTab(AppTab)
case requestNotebookRoot
case requestProfileRoot
Expand Down Expand Up @@ -1217,14 +1220,15 @@ struct AppModel: ModelProtocol {
state: state,
environment: environment
)
case .succeedMoveEntry(from: let from, to: let to):
return Update(state: state)
case .succeedMergeEntry(parent: let parent, child: let child):
case .succeedMoveEntry, .succeedMergeEntry, .succeedLogActivity, .succeedUpdateAudience:
return Update(state: state)
case .succeedSaveEntry(address: let address, modified: let modified):
return Update(state: state)
case .succeedUpdateAudience(_):
return Update(state: state)
return succeedSaveEntry(
state: state,
environment: environment,
address: address,
modified: modified
)
case let .saveEntry(entry):
return saveEntry(
state: state,
Expand Down Expand Up @@ -1309,6 +1313,13 @@ struct AppModel: ModelProtocol {
),
environment: environment
)
case let .failLogActivity(error):
logger.warning(
"""
Failed to log activity: \(error)
"""
)
return Update(state: state)
}
}

Expand Down Expand Up @@ -2967,6 +2978,43 @@ struct AppModel: ModelProtocol {
return Update(state: state, fx: fx)
}

struct SucceedSaveEntryActivityEvent: Codable {
public static let event = "save_entry"
public let address: String
}

static func succeedSaveEntry(
state: Self,
environment: AppEnvironment,
address: Slashlink,
modified: Date
) -> Update<Self> {
let fx: Fx<AppAction> = Future.detached {
try environment.database.writeActivity(
event: ActivityEvent(
category: .system,
event: SucceedSaveEntryActivityEvent.event,
message: "",
metadata: SucceedSaveEntryActivityEvent(address: address.description)
)
)

return .succeedLogActivity
}
.recover { error in
.failLogActivity(error.localizedDescription)
}
.eraseToAnyPublisher()

logger.log(
"Saved entry",
metadata: [
"address": address.description
]
)
return Update(state: state, fx: fx)
}

static func mergeEntry(
state: Self,
environment: AppEnvironment,
Expand Down
28 changes: 23 additions & 5 deletions xcode/Subconscious/Shared/Components/Deck/DeckView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -586,11 +586,29 @@ struct DeckModel: ModelProtocol {

return Update(state: state)
}


func shuffleInBacklinks(_ backlinks: Set<EntryStub>) -> Update<Self> {
struct ChooseCardActivityEvent: Codable {
public static let event: String = "choose_card"

let address: String
}

func shuffleInBacklinks(message: String, address: Slashlink, backlinks: Set<EntryStub>) -> Update<Self> {
let fx: Fx<DeckAction> = Future.detached {
let us = try await environment.noosphere.identity()

try environment.database.writeActivity(
event: ActivityEvent(
category: .deck,
event: ChooseCardActivityEvent.event,
message: message,
metadata: ChooseCardActivityEvent(
address: address.description
)
)
)

// Filter to valid backlinks
let backlinks = backlinks
.filter({ backlink in !isSeen(entry: backlink) })
Expand Down Expand Up @@ -629,10 +647,10 @@ struct DeckModel: ModelProtocol {
state.feedback.impactOccurred()

switch card.card {
case let .entry(_, _, related):
return shuffleInBacklinks(related)
case let .prompt(_, _, _, related):
return shuffleInBacklinks(related)
case let .entry(entry, _, related):
return shuffleInBacklinks(message: "", address: entry.address, backlinks: related)
case let .prompt(message, entry, _, related):
return shuffleInBacklinks(message: message, address: entry.address, backlinks: related)
case .action(let msg):
logger.log("Action: \(msg)")
return update(
Expand Down
22 changes: 22 additions & 0 deletions xcode/Subconscious/Shared/Models/ActivityEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// ActivityEvent.swift
// Subconscious (iOS)
//
// Created by Ben Follington on 10/1/2024.
//

import Foundation

struct ActivityEvent<Metadata: Codable>: Codable {
let category: ActivityEventCategory
let event: String
let message: String
let metadata: Metadata?
}

enum ActivityEventCategory: String, Codable {
case system = "system"
case deck = "deck"
case note = "note"
case addressBook = "addressBook"
}
44 changes: 44 additions & 0 deletions xcode/Subconscious/Shared/Services/AddressBookService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,13 @@ actor AddressBookService {
.eraseToAnyPublisher()
}

struct FollowUserActivityEvent: Codable {
public static let event: String = "follow_user"

let did: String
let petname: String
}

/// Associates the passed DID with the passed petname within the sphere,
/// saves the changes and updates the database.
func followUser(
Expand All @@ -344,6 +351,23 @@ actor AddressBookService {

try await noosphere.setPetname(did: did, petname: petname)
let _ = try await noosphere.save()

try database.writeActivity(
event: ActivityEvent(
category: .addressBook,
event: FollowUserActivityEvent.event,
message: "Followed \(petname.markup)",
metadata: FollowUserActivityEvent(
did: did.did.description,
petname: petname.description
)
)
)
}

struct AddressBookActivityEventMetadata: Codable {
let did: String
let petname: String
}

func resolutionStatus(petname: Petname) async throws -> ResolutionStatus {
Expand Down Expand Up @@ -416,12 +440,32 @@ actor AddressBookService {
.eraseToAnyPublisher()
}

struct UnfollowUserActivityEvent: Codable {
public static let event: String = "unfollow_user"

let did: String
let petname: String
}

/// Disassociates the passed Petname from any DID within the sphere,
/// saves the changes and updates the database.
func unfollowUser(petname: Petname) async throws -> Did {
let did = try await self.noosphere.getPetname(petname: petname)
try await self.addressBook.unsetPetname(petname: petname)
let _ = try await self.noosphere.save()

try database.writeActivity(
event: ActivityEvent(
category: .addressBook,
event: UnfollowUserActivityEvent.event,
message: "Unfollowed \(petname.markup)",
metadata: UnfollowUserActivityEvent(
did: did.description,
petname: petname.description
)
)
)

return did
}

Expand Down
37 changes: 31 additions & 6 deletions xcode/Subconscious/Shared/Services/DataService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ actor DataService {
/// - If sphere has not been indexed, we get everything, and update
func indexPeer(
petname: Petname
) async throws -> PeerRecord {
) async throws -> (Int, PeerRecord) {
let sphere = try await noosphere.traverse(petname: petname)
let identity = try await sphere.identity()
logger.log(
Expand Down Expand Up @@ -147,6 +147,7 @@ actor DataService {

let savepoint = "index_peer"
try database.savepoint(savepoint)

do {
for change in changes {
let slashlink = Slashlink(slug: change)
Expand Down Expand Up @@ -212,13 +213,23 @@ actor DataService {
)
throw error
}
return PeerRecord(
petname: petname,
identity: identity,
since: version
return (
changes.count,
PeerRecord(
petname: petname,
identity: identity,
since: version
)
)
}

struct SucceedIndexPeerActivityEvent: Codable {
public static let event = "succeed_index_peer"
let did: String
let petname: String
let changes: Int
}

func indexPeers(petnames: [Petname]) async -> [PeerIndexResult] {
var results: [PeerIndexResult] = []

Expand All @@ -231,11 +242,25 @@ actor DataService {
)

do {
let peer = try await self.indexPeer(
let (changeCount, peer) = try await self.indexPeer(
petname: petname
)

results.append(.success(peer))

try database.writeActivity(
event: ActivityEvent(
category: .system,
event: SucceedIndexPeerActivityEvent.event,
message: "Found \(changeCount) updates from \(petname.markup)",
metadata: SucceedIndexPeerActivityEvent(
did: peer.identity.description,
petname: petname.description,
changes: changeCount
)
)
)

// Give other tasks a chance to use noosphere, indexing many peers
// can take a long time and potentially block user actions
await Task.yield()
Expand Down
38 changes: 36 additions & 2 deletions xcode/Subconscious/Shared/Services/DatabaseService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1338,7 +1338,8 @@ final class DatabaseService {

let dids = [
Did.local.description,
owner.description]
owner.description
]

return try? database.execute(
sql: """
Expand Down Expand Up @@ -1371,9 +1372,27 @@ final class DatabaseService {
})
.first
}

func writeActivity<Data: Codable>(event: ActivityEvent<Data>) throws {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a test for this method using in-memory SQL

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep 👍

guard self.state == .ready else {
return
}

try database.execute(
sql: """
INSERT INTO activity (category, event, message, metadata)
VALUES (?, ?, ?, ?)
""",
parameters: [
.text(event.category.rawValue),
.text(event.event),
.text(event.message),
.json(event.metadata, or: "{}")
]
)
}
}


// MARK: Migrations
extension Config {
static let migrations = Migrations([
Expand Down Expand Up @@ -1555,6 +1574,21 @@ extension Config {
);
END;
"""
),
SQLMigration(
version: Int.from(iso8601String: "2024-01-10T11:59:00")!,
sql: """
/* Tracks `ActivityEvent`s in a queryable stream */
CREATE TABLE activity
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT NOT NULL,
event TEXT NOT NULL,
message TEXT DEFAULT '' NOT NULL,
metadata TEXT DEFAULT '{}' NOT NULL,
created TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL
);
"""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to also add a test for these migrations using a fresh in-memory SQL DB? This is a question. I don't recall if I put together the code to make that work.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

)
])
}
4 changes: 4 additions & 0 deletions xcode/Subconscious/Subconscious.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
B58FD6732A4E4C0E00826548 /* InviteCodeSettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58FD6722A4E4C0E00826548 /* InviteCodeSettingsSection.swift */; };
B58FD6752A4E4C8200826548 /* ResourceSyncBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58FD6742A4E4C8200826548 /* ResourceSyncBadge.swift */; };
B5908BEB29DAB05B00225B1A /* TestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5908BEA29DAB05B00225B1A /* TestUtilities.swift */; };
B597C7372B4E34DB003F4342 /* ActivityEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B597C7362B4E34DA003F4342 /* ActivityEvent.swift */; };
B59D556329BBFF56007915E2 /* FormField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59D556229BBFF56007915E2 /* FormField.swift */; };
B59E4E572B3BA781000A2B49 /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59E4E562B3BA781000A2B49 /* CardView.swift */; };
B59E4E592B3BA8E7000A2B49 /* EntryCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59E4E582B3BA8E7000A2B49 /* EntryCardView.swift */; };
Expand Down Expand Up @@ -629,6 +630,7 @@
B58FD6722A4E4C0E00826548 /* InviteCodeSettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteCodeSettingsSection.swift; sourceTree = "<group>"; };
B58FD6742A4E4C8200826548 /* ResourceSyncBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceSyncBadge.swift; sourceTree = "<group>"; };
B5908BEA29DAB05B00225B1A /* TestUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtilities.swift; sourceTree = "<group>"; };
B597C7362B4E34DA003F4342 /* ActivityEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityEvent.swift; sourceTree = "<group>"; };
B59D556229BBFF56007915E2 /* FormField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormField.swift; sourceTree = "<group>"; };
B59E4E562B3BA781000A2B49 /* CardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; };
B59E4E582B3BA8E7000A2B49 /* EntryCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryCardView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1441,6 +1443,7 @@
B5E816B82AB0458E00C61500 /* RecoveryPhrase.swift */,
B51359292AC2723E004385A7 /* GatewayURL.swift */,
B5EC3A4E2B297DBA00FDF728 /* Prompt.swift */,
B597C7362B4E34DA003F4342 /* ActivityEvent.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -2167,6 +2170,7 @@
B542EF772AF495F200BE29F1 /* FollowNewUserSheetModifier.swift in Sources */,
B8A617202971D3000054D410 /* Slashlink.swift in Sources */,
B81EACD827B724A000B3B8DC /* ThickDividerView.swift in Sources */,
B597C7372B4E34DB003F4342 /* ActivityEvent.swift in Sources */,
B5F68F602A09F7E200CE4DD7 /* StackedGlowingImage.swift in Sources */,
B58862C829F612CE006C2EE4 /* EditProfileSheet.swift in Sources */,
B828F0A32AFFF05800FDE4AE /* TranscludeListView.swift in Sources */,
Expand Down
Loading