From 194312778352586e1a21b29486899c9687fa92ba Mon Sep 17 00:00:00 2001 From: Austin Fatheree Date: Thu, 4 Jul 2024 13:24:44 -0500 Subject: [PATCH 1/2] Added Migration Pattern --- src/main.mo | 274 ++++++++++----------------- src/migrations/lib.mo | 51 +++++ src/migrations/types.mo | 18 ++ src/migrations/v000_000_000/lib.mo | 8 + src/migrations/v000_000_000/types.mo | 10 + src/migrations/v000_001_000/lib.mo | 31 +++ src/migrations/v000_001_000/types.mo | 43 +++++ src/service.mo | 28 +++ src/you.mo | 247 ++++++++++++++++++++++++ 9 files changed, 531 insertions(+), 179 deletions(-) create mode 100644 src/migrations/lib.mo create mode 100644 src/migrations/types.mo create mode 100644 src/migrations/v000_000_000/lib.mo create mode 100644 src/migrations/v000_000_000/types.mo create mode 100644 src/migrations/v000_001_000/lib.mo create mode 100644 src/migrations/v000_001_000/types.mo create mode 100644 src/service.mo create mode 100644 src/you.mo diff --git a/src/main.mo b/src/main.mo index b9d206f..0e94b12 100644 --- a/src/main.mo +++ b/src/main.mo @@ -10,8 +10,12 @@ import Int "mo:base/Int"; import Result "mo:base/Result"; import Bool "mo:base/Bool"; import Float "mo:base/Float"; +import Debug "mo:base/Debug"; import Http "http"; import Prim "mo:⛔"; +import You "you"; +import D "mo:base/Debug"; +import Service "service"; shared ({ caller = creator }) actor class UserCanister( yourName : Text @@ -23,249 +27,143 @@ shared ({ caller = creator }) actor class UserCanister( stable let owner : Principal = creator; stable let name : Name = yourName; - public type Mood = Text; - public type Name = Text; - public type Friend = Friends.Friend; - public type FriendRequest = Friends.FriendRequest; + stable var alive : Bool = true; + stable var latestPing : Time.Time = Time.now(); + + stable var friendRequestId : Nat = 0; + + + stable var friends : [Friends.Friend] = []; + + stable var messagesId : Nat = 0; + + + public type Mood = You.Mood; + public type Name = You.Name; + public type Friend = You.Friend; + public type FriendRequest = You.FriendRequest; + public type FriendRequestResult = Friends.FriendRequestResult; public type Result = Result.Result; - stable var alive : Bool = true; - stable var latestPing : Time.Time = Time.now(); + var you_ : ?You.You = null; - // Function to kill the user if they haven't pinged in 24 hours - func _kill() : async () { - let now = Time.now(); - if (now - latestPing > NANOSECONDS_PER_DAY) { - alive := false; + private func you() : You.You { + switch (you_) { + case (?you) { return you; }; + case (null) { + let you = You.You(null, Principal.fromActor(this), ()); + you_ := ?you; + + return you; + }; }; }; - // Timer to reset the alive status every 24 hours - let _daily = Timer.recurringTimer(#nanoseconds(NANOSECONDS_PER_DAY), _kill); - public query func reboot_user_isAlive() : async Bool { - return alive; + let ?youClass = you_ else D.trap("init needed"); + return youClass.state.alive; }; - // Import the board actor and related types - public type WriteError = { - #NotEnoughCycles; - #MemoryFull; - #NameTooLong; - #MoodTooLong; - #NotAllowed; + public shared(msg) func _init() : async() { + switch (you_) { + case (?you) { return; }; + case (null) { + ignore you(); + you().initTimer(); + }; + }; }; - public type MessageError = { - #NotEnoughCycles; - #MemoryFull; - #MessageTooLong; - #NotAllowed; + func callInit() : async () { + let myActor : actor{ + _init : () -> async (); + } = actor(Principal.toText(Principal.fromActor(this))); + await myActor._init(); }; - let board = actor ("q3gy3-sqaaa-aaaas-aaajq-cai") : actor { - reboot_board_write : (name : Name, mood : Mood) -> async Result<(), WriteError>; - }; + ignore Timer.setTimer(#nanoseconds(0), callInit); + + // Import the board actor and related types + public type WriteError = Service.WriteError; + + public type MessageError = Service.MessageError; + + public shared ({ caller }) func reboot_user_dailyCheck( mood : Mood ) : async Result<(), WriteError> { - assert (caller == owner); - alive := true; - latestPing := Time.now(); - - // Write the daily check to the board - try { - Cycles.add(1_000_000_000); - await board.reboot_board_write(name, mood); - } catch (e) { - throw e; - }; + return await* you().reboot_user_dailyCheck(caller, mood); }; - stable var friendRequestId : Nat = 0; - var friendRequests : [Friends.FriendRequest] = []; - - stable var friends : [Friends.Friend] = []; - - stable var messagesId : Nat = 0; - var messages : [(Nat, Name, Text)] = []; - public shared ({ caller }) func reboot_user_receiveFriendRequest( name : Text, message : Text, ) : async FriendRequestResult { - // Check if there is enough cycles attached (Fee for Friend Request) and accept them - let availableCycles = Cycles.available(); - let acceptedCycles = Cycles.accept(availableCycles); - if (acceptedCycles < 1_000_000_000) { - return #err(#NotEnoughCycles); - }; - - let request : FriendRequest = { - id = friendRequestId; - name = name; - sender = caller; - message = message; - }; - - // Check if the user is already a friend - for (friend in friends.vals()) { - if (friend.canisterId == caller) { - return #err(#AlreadyFriend); - }; - }; - - // Check if the user has already sent a friend request - for (request in friendRequests.vals()) { - if (request.sender == caller) { - return #err(#AlreadyRequested); - }; - }; - - friendRequests := Array.append(friendRequests, [request]); - friendRequestId += 1; - return #ok(); + return await* you().reboot_user_receiveFriendRequest(caller, name, message); }; public shared ({ caller }) func reboot_user_sendFriendRequest( receiver : Principal, message : Text, ) : async FriendRequestResult { - assert (caller == owner); - // Create the actor reference - let friendActor = actor (Principal.toText(receiver)) : actor { - reboot_user_receiveFriendRequest : (name : Text, message : Text) -> async FriendRequestResult; - }; - // Attach the cycles to the call (1 billion cycles) - Cycles.add(1_000_000_000); - // Call the function (handle potential errors) - try { - return await friendActor.reboot_user_receiveFriendRequest(name, message); - } catch (e) { - throw e; - }; + return await* you().reboot_user_sendFriendRequest(caller, receiver, message); }; public shared query ({ caller }) func reboot_user_getFriendRequests() : async [FriendRequest] { - assert (caller == owner); - return friendRequests; + assert (caller == you().state.owner); + return you().state.friendRequests; }; public shared ({ caller }) func reboot_user_handleFriendRequest( id : Nat, accept : Bool, ) : async Result<(), Text> { - assert (caller == owner); - // Check that the friend request exists - for (request in friendRequests.vals()) { - if (request.id == id) { - // If the request is accepted - if (accept) { - // Add the friend to the list - friends := Array.append(friends, [{ name = request.name; canisterId = request.sender }]); - // Remove the request from the list - friendRequests := Array.filter(friendRequests, func(request : FriendRequest) { request.id != id }); - return #ok(); - } else { - // Remove the request from the list - friendRequests := Array.filter(friendRequests, func(request : FriendRequest) { request.id != id }); - return #ok(); - }; - }; - }; - return #err("Friend request not found for id " # Nat.toText(id)); + return await* you().reboot_user_handleFriendRequest(caller, id, accept); }; public shared ({ caller }) func reboot_user_getFriends() : async [Friend] { - assert (caller == owner); - return friends; + assert (caller == you().state.owner); + return you().state.friends; }; public shared ({ caller }) func reboot_user_removeFriend( canisterId : Principal ) : async Result<(), Text> { - assert (caller == owner); - for (friend in friends.vals()) { - if (friend.canisterId == canisterId) { - friends := Array.filter(friends, func(x : Friend) { x.canisterId != canisterId }); - return #ok(); - }; - }; - return #err("Friend not found with canisterId " # Principal.toText(canisterId)); + return await* you().reboot_user_removeFriend(caller, canisterId); }; public shared ({ caller }) func reboot_user_sendMessage( receiver : Principal, message : Text, ) : async Result<(), MessageError> { - assert (caller == owner); - // Create the actor reference - let friendActor = actor (Principal.toText(receiver)) : actor { - reboot_user_receiveMessage : (message : Text) -> async Result<(), MessageError>; - }; - // Attach the cycles to the call (1 billion cycles) - Cycles.add(1_000_000_000); - // Call the function (handle potential errors) - try { - return await friendActor.reboot_user_receiveMessage(message); - } catch (e) { - throw e; - }; + return await* you().reboot_user_sendMessage(caller, receiver, message); }; public shared ({ caller }) func reboot_user_receiveMessage( message : Text ) : async Result<(), MessageError> { - // Check if there is enough cycles attached (Fee for Message) and accept them - let availableCycles = Cycles.available(); - let acceptedCycles = Cycles.accept(availableCycles); - if (acceptedCycles < 1_000_000_000) { - return #err(#NotEnoughCycles); - }; - // Check that the message is not too long - if (message.size() > 1024) { - return #err(#MessageTooLong); - }; - - // Check if the caller is already a friend - for (friend in friends.vals()) { - if (friend.canisterId == caller) { - messages := Array.append<(Nat, Name, Text)>(messages, [(messagesId, friend.name, message)]); - messagesId += 1; - return #ok(); - }; - }; - return #err(#NotAllowed); + return await* you().reboot_user_receiveMessage(caller, message); }; public shared ({ caller }) func reboot_user_readMessages() : async [(Nat, Name, Text)] { - assert (caller == owner); - return messages; + assert (caller == you().state.owner); + return you().state.messages; }; public shared ({ caller }) func reboot_user_clearMessage( id : Nat ) : async Result<(), Text> { - assert (caller == owner); - for (message in messages.vals()) { - if (message.0 == id) { - messages := Array.filter<(Nat, Name, Text)>(messages, func(x : (Nat, Name, Text)) { x.0 != id }); - return #ok(); - }; - }; - return #err("Message not found with id " # Nat.toText(id)); + return await* you().reboot_user_clearMessage(caller, id); }; public shared ({ caller }) func reboot_user_clearAllMessages() : async Result<(), Text> { - assert (caller == owner); - messages := []; - return #ok(); + return await* you().reboot_user_clearAllMessages(caller); }; public query func reboot_user_version() : async (Nat, Nat, Nat) { - return version; + return you().state.version; }; public type HttpRequest = Http.Request; @@ -275,14 +173,14 @@ shared ({ caller = creator }) actor class UserCanister( body = Text.encodeUtf8( "You\n" # "---\n" - # "Name: " # name # "\n" - # "Owner: " # Principal.toText(owner) # "\n" - # "Birth: " # Int.toText(birth) # "\n" - # "Alive: " # Bool.toText(alive) # "\n" - # "Friends: " # Nat.toText(friends.size()) # "\n" - # "Pending requests: " # Nat.toText(friendRequests.size()) # "\n" - # "Pending messages: " # Nat.toText(messages.size()) # "\n" - # "Version: " # Nat.toText(version.0) # "." # Nat.toText(version.1) # "." # Nat.toText(version.2) # "\n" + # "Name: " # you().state.name # "\n" + # "Owner: " # Principal.toText(you().state.owner) # "\n" + # "Birth: " # Int.toText(you().state.birth) # "\n" + # "Alive: " # Bool.toText(you().state.alive) # "\n" + # "Friends: " # Nat.toText(you().state.friends.size()) # "\n" + # "Pending requests: " # Nat.toText(you().state.friendRequests.size()) # "\n" + # "Pending messages: " # Nat.toText(you().state.messages.size()) # "\n" + # "Version: " # Nat.toText(you().state.version.0) # "." # Nat.toText(you().state.version.1) # "." # Nat.toText(you().state.version.2) # "\n" # "Cycle Balance: " # Nat.toText(Cycles.balance()) # " cycles " # "(" # Nat.toText(Cycles.balance() / 1_000_000_000_000) # " T" # ")\n" # "Heap size (current): " # Nat.toText(Prim.rts_heap_size()) # " bytes " # "(" # Float.toText(Float.fromInt(Prim.rts_heap_size() / (1024 * 1024))) # " Mb" # ")\n" # "Heap size (max): " # Nat.toText(Prim.rts_max_live_size()) # " bytes " # "(" # Float.toText(Float.fromInt(Prim.rts_max_live_size() / (1024 * 1024))) # " Mb" # ")\n" @@ -293,4 +191,22 @@ shared ({ caller = creator }) actor class UserCanister( }); }; + system func postupgrade() { + you().state := { + version = (0, 0, 2); + birth = birth; + owner = owner; + var alive = alive; + var name = yourName; + var mood = "not set"; + var latestPing = latestPing; + var friendRequestId = friendRequestId; + var friendRequests = []; + var friends = friends; + var messages = []; + var messagesId = messagesId; + }; + + }; + }; diff --git a/src/migrations/lib.mo b/src/migrations/lib.mo new file mode 100644 index 0000000..06c4e36 --- /dev/null +++ b/src/migrations/lib.mo @@ -0,0 +1,51 @@ +import D "mo:base/Debug"; + +import MigrationTypes "./types"; +import v0_0_0 "./v000_000_000"; +import v0_1_0 "./v000_001_000"; + +module { + + let debug_channel = { + announce = true; + }; + + let upgrades = [ + v0_1_0.upgrade, + // do not forget to add your new migration upgrade method here + ]; + + func getMigrationId(state: MigrationTypes.State): Nat { + return switch (state) { + case (#v0_0_0(_)) 0; + case (#v0_1_0(_)) 1; + // do not forget to add your new migration id here + // should be increased by 1 as it will be later used as an index to get upgrade/downgrade methods + }; + }; + + public func migrate( + prevState: MigrationTypes.State, + nextState: MigrationTypes.State, + args: MigrationTypes.Args, + caller: Principal + ): MigrationTypes.State { + + + var state = prevState; + + var migrationId = getMigrationId(prevState); + let nextMigrationId = getMigrationId(nextState); + + while (nextMigrationId > migrationId) { + debug if (debug_channel.announce) D.print("in upgrade while" # debug_show((nextMigrationId, migrationId))); + let migrate = upgrades[migrationId]; + debug if (debug_channel.announce) D.print("upgrade should have run"); + migrationId := if (nextMigrationId > migrationId) migrationId + 1 else migrationId - 1; + + state := migrate(state, args, caller); + }; + + return state; + }; +}; \ No newline at end of file diff --git a/src/migrations/types.mo b/src/migrations/types.mo new file mode 100644 index 0000000..a26dd71 --- /dev/null +++ b/src/migrations/types.mo @@ -0,0 +1,18 @@ +import v0_1_0 "./v000_001_000/types"; +import Int "mo:base/Int"; + + +module { + // do not forget to change current migration when you add a new one + // you should use this field to import types from you current migration anywhere in your project + // instead of importing it from migration folder itself + public let Current = v0_1_0; + + public type Args = ?v0_1_0.InitArgs; + + public type State = { + #v0_0_0: {#id; #data}; + #v0_1_0: {#id; #data: v0_1_0.State}; + // do not forget to add your new migration state types here + }; +}; \ No newline at end of file diff --git a/src/migrations/v000_000_000/lib.mo b/src/migrations/v000_000_000/lib.mo new file mode 100644 index 0000000..a68179b --- /dev/null +++ b/src/migrations/v000_000_000/lib.mo @@ -0,0 +1,8 @@ +import MigrationTypes "../types"; +import D "mo:base/Debug"; + +module { + public func upgrade(prevmigration_state: MigrationTypes.State, args: MigrationTypes.Args, caller: Principal): MigrationTypes.State { + return #v0_0_0(#data); + }; +}; \ No newline at end of file diff --git a/src/migrations/v000_000_000/types.mo b/src/migrations/v000_000_000/types.mo new file mode 100644 index 0000000..b4d8c04 --- /dev/null +++ b/src/migrations/v000_000_000/types.mo @@ -0,0 +1,10 @@ + +// please do not import any types from your project outside migrations folder here +// it can lead to bugs when you change those types later, because migration types should not be changed +// you should also avoid importing these types anywhere in your project directly from here +// use MigrationTypes.Current property instead + + +module { + public type State = (); +}; \ No newline at end of file diff --git a/src/migrations/v000_001_000/lib.mo b/src/migrations/v000_001_000/lib.mo new file mode 100644 index 0000000..94b6dde --- /dev/null +++ b/src/migrations/v000_001_000/lib.mo @@ -0,0 +1,31 @@ +import MigrationTypes "../types"; +import Time "mo:base/Time"; +import v0_1_0 "types"; +import D "mo:base/Debug"; + +module { + public func upgrade(prevmigration_state: MigrationTypes.State, args: MigrationTypes.Args, caller: Principal): MigrationTypes.State { + + let (name) = switch (args) { + case (?args) {(args.name)}; + case (_) {("nobody")}; + }; + + let state : v0_1_0.State = { + version = (0,2,0); + birth = Time.now(); + owner = caller; + var name = name; + var alive = true; + var mood = "not set"; + var latestPing = Time.now(); + var friendRequestId = 0; + var friendRequests = []; + var friends = []; + var messagesId = 0; + var messages = []; + }; + + return #v0_1_0(#data(state)); + }; +}; \ No newline at end of file diff --git a/src/migrations/v000_001_000/types.mo b/src/migrations/v000_001_000/types.mo new file mode 100644 index 0000000..c31578a --- /dev/null +++ b/src/migrations/v000_001_000/types.mo @@ -0,0 +1,43 @@ +import Time "mo:base/Time"; +// please do not import any types from your project outside migrations folder here +// it can lead to bugs when you change those types later, because migration types should not be changed +// you should also avoid importing these types anywhere in your project directly from here +// use MigrationTypes.Current property instead + + +module { + + public type Name = Text; + public type Mood = Text; + + public type Friend = { + name : Text; + canisterId : Principal; + }; + + public type FriendRequest = { + id : Nat; + name : Text; + sender : Principal; + message : Text; + }; + + public type InitArgs ={ + name: Text; + }; + public type Environment = (); + public type State = { + version : (Nat, Nat, Nat); + birth : Time.Time; + owner : Principal; + var name : Name; + var mood : Mood; + var alive : Bool; + var latestPing : Time.Time; + var friends : [Friend]; + var friendRequestId: Nat; + var friendRequests : [FriendRequest]; + var messagesId : Nat; + var messages : [(Nat, Name, Text)] + }; +}; \ No newline at end of file diff --git a/src/service.mo b/src/service.mo new file mode 100644 index 0000000..fe9c558 --- /dev/null +++ b/src/service.mo @@ -0,0 +1,28 @@ +import Result "mo:base/Result"; + +module{ + + public type WriteError = { + #NotEnoughCycles; + #MemoryFull; + #NameTooLong; + #MoodTooLong; + #NotAllowed; + }; + + public type MessageError = { + #NotEnoughCycles; + #MemoryFull; + #MessageTooLong; + #NotAllowed; + }; + + public type FriendRequestError = { + #AlreadyFriend; + #AlreadyRequested; + #NotEnoughCycles; + }; + + public type FriendRequestResult = Result.Result<(), FriendRequestError>; + +} \ No newline at end of file diff --git a/src/you.mo b/src/you.mo new file mode 100644 index 0000000..bbd17a2 --- /dev/null +++ b/src/you.mo @@ -0,0 +1,247 @@ +import MigrationTypes "migrations/types"; +import Migration "migrations"; +import Time "mo:base/Time"; +import Timer "mo:base/Timer"; +import Result "mo:base/Result"; +import Service "service"; +import Cycles "mo:base/ExperimentalCycles"; +import Principal "mo:base/Principal"; +import Array "mo:base/Array"; +import Nat "mo:base/Nat"; + +module { + + public type State = MigrationTypes.State; + + public type CurrentState = MigrationTypes.Current.State; + + public type Environment = MigrationTypes.Current.Environment; + + public type Name = MigrationTypes.Current.Name; + public type Mood = MigrationTypes.Current.Mood; + public type Friend = MigrationTypes.Current.Friend; + public type FriendRequest = MigrationTypes.Current.FriendRequest; + + public func initialState() : State {#v0_0_0(#data)}; + public let currentStateVersion = #v0_1_0(#id); + + public let NANOSECONDS_PER_DAY = 86400000000000; + + let board = actor ("q3gy3-sqaaa-aaaas-aaajq-cai") : actor { + reboot_board_write : (name : Name, mood : Mood) -> async Result.Result<(), Service.WriteError>; + }; + + + + + public class You(stored: ?State, canister: Principal, environment: Environment){ + + /// Initializes the ledger state with either a new state or a given state for migration. + /// This setup process involves internal data migration routines. + public var state : CurrentState = switch(stored){ + case(null) { + let #v0_1_0(#data(foundState)) = Migration.migrate(initialState(), currentStateVersion, null, canister); + foundState; + }; + case(?val) { + let #v0_1_0(#data(foundState)) = Migration.migrate(val, currentStateVersion, null, canister); + foundState; + }; + }; + + // Function to kill the user if they haven't pinged in 24 hours + private func _kill() : async () { + let now = Time.now(); + if (now - state.latestPing > NANOSECONDS_PER_DAY) { + state.alive := false; + }; + }; + + // Timer to reset the alive status every 24 hours + public func initTimer(){ + let _daily = Timer.recurringTimer(#nanoseconds(NANOSECONDS_PER_DAY), _kill); + }; + + public func reboot_user_dailyCheck(caller: Principal, + mood : Mood + ) : async* Result.Result<(), Service.WriteError> { + assert (caller == state.owner); + state.alive := true; + state.latestPing := Time.now(); + state.mood := mood; + + // Write the daily check to the board + try { + Cycles.add(1_000_000_000); + await board.reboot_board_write(state.name, mood); + } catch (e) { + throw e; + }; + }; + + public func reboot_user_receiveFriendRequest( + caller: Principal, + name : Text, + message : Text, + ) : async* Service.FriendRequestResult { + // Check if there is enough cycles attached (Fee for Friend Request) and accept them + let availableCycles = Cycles.available(); + let acceptedCycles = Cycles.accept(availableCycles); + if (acceptedCycles < 1_000_000_000) { + return #err(#NotEnoughCycles); + }; + + let request : FriendRequest = { + id = state.friendRequestId; + name = name; + sender = caller; + message = message; + }; + + // Check if the user is already a friend + for (friend in state.friends.vals()) { + if (friend.canisterId == caller) { + return #err(#AlreadyFriend); + }; + }; + + // Check if the user has already sent a friend request + for (request in state.friendRequests.vals()) { + if (request.sender == caller) { + return #err(#AlreadyRequested); + }; + }; + + state.friendRequests := Array.append(state.friendRequests, [request]); + state.friendRequestId += 1; + return #ok(); + }; + + public func reboot_user_sendFriendRequest(caller: Principal, + receiver : Principal, + message : Text, + ) : async* Service.FriendRequestResult { + assert (caller == state.owner); + // Create the actor reference + let friendActor = actor (Principal.toText(receiver)) : actor { + reboot_user_receiveFriendRequest : (name : Text, message : Text) -> async Service.FriendRequestResult; + }; + // Attach the cycles to the call (1 billion cycles) + Cycles.add(1_000_000_000); + // Call the function (handle potential errors) + try { + return await friendActor.reboot_user_receiveFriendRequest(state.name, message); + } catch (e) { + throw e; + }; + }; + + public func reboot_user_handleFriendRequest( + caller: Principal, + id : Nat, + accept : Bool, + ) : async* Result.Result<(), Text> { + assert (caller == state.owner); + // Check that the friend request exists + for (request in state.friendRequests.vals()) { + if (request.id == id) { + // If the request is accepted + if (accept) { + // Add the friend to the list + state.friends := Array.append(state.friends, [{ name = request.name; canisterId = request.sender }]); + // Remove the request from the list + state.friendRequests := Array.filter(state.friendRequests, func(request : FriendRequest) { request.id != id }); + return #ok(); + } else { + // Remove the request from the list + state.friendRequests := Array.filter(state.friendRequests, func(request : FriendRequest) { request.id != id }); + return #ok(); + }; + }; + }; + return #err("Friend request not found for id " # Nat.toText(id)); + }; + + public func reboot_user_removeFriend( + caller: Principal, + canisterId : Principal + ) : async* Result.Result<(), Text> { + assert (caller == state.owner); + for (friend in state.friends.vals()) { + if (friend.canisterId == canisterId) { + state.friends := Array.filter(state.friends, func(x : Friend) { x.canisterId != canisterId }); + return #ok(); + }; + }; + return #err("Friend not found with canisterId " # Principal.toText(canisterId)); + }; + + public func reboot_user_sendMessage( + caller: Principal, + receiver : Principal, + message : Text, + ) : async* Result.Result<(), Service.MessageError> { + assert (caller == state.owner); + // Create the actor reference + let friendActor = actor (Principal.toText(receiver)) : actor { + reboot_user_receiveMessage : (message : Text) -> async Result.Result<(), Service.MessageError>; + }; + // Attach the cycles to the call (1 billion cycles) + Cycles.add(1_000_000_000); + // Call the function (handle potential errors) + try { + return await friendActor.reboot_user_receiveMessage(message); + } catch (e) { + throw e; + }; + }; + + public func reboot_user_receiveMessage( + caller: Principal, + message : Text + ) : async* Result.Result<(), Service.MessageError> { + // Check if there is enough cycles attached (Fee for Message) and accept them + let availableCycles = Cycles.available(); + let acceptedCycles = Cycles.accept(availableCycles); + if (acceptedCycles < 1_000_000_000) { + return #err(#NotEnoughCycles); + }; + // Check that the message is not too long + if (message.size() > 1024) { + return #err(#MessageTooLong); + }; + + // Check if the caller is already a friend + for (friend in state.friends.vals()) { + if (friend.canisterId == caller) { + state.messages := Array.append<(Nat, Name, Text)>(state.messages, [(state.messagesId, friend.name, message)]); + state.messagesId += 1; + return #ok(); + }; + }; + return #err(#NotAllowed); + }; + + public func reboot_user_clearMessage( + caller: Principal, + id : Nat + ) : async* Result.Result<(), Text> { + assert (caller == state.owner); + for (message in state.messages.vals()) { + if (message.0 == id) { + state.messages := Array.filter<(Nat, Name, Text)>(state.messages, func(x : (Nat, Name, Text)) { x.0 != id }); + return #ok(); + }; + }; + return #err("Message not found with id " # Nat.toText(id)); + }; + + public func reboot_user_clearAllMessages(caller: Principal) : async* Result.Result<(), Text> { + assert (caller == state.owner); + state.messages := []; + return #ok(); + }; + + + }; +} \ No newline at end of file From aae5f4363e8a964afd7723f6ba8769b15ac1864c Mon Sep 17 00:00:00 2001 From: Austin Fatheree Date: Thu, 4 Jul 2024 15:00:01 -0500 Subject: [PATCH 2/2] Initialization --- src/main.mo | 6 +++++- src/you.mo | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main.mo b/src/main.mo index 0e94b12..7524800 100644 --- a/src/main.mo +++ b/src/main.mo @@ -46,13 +46,17 @@ shared ({ caller = creator }) actor class UserCanister( public type FriendRequestResult = Friends.FriendRequestResult; public type Result = Result.Result; + stable let youState = You.init(#v0_0_0(#data),#v0_1_0(#id), ?{ + name = yourName; + }, creator); + var you_ : ?You.You = null; private func you() : You.You { switch (you_) { case (?you) { return you; }; case (null) { - let you = You.You(null, Principal.fromActor(this), ()); + let you = You.You(?youState, Principal.fromActor(this), ()); you_ := ?you; return you; diff --git a/src/you.mo b/src/you.mo index bbd17a2..32b2723 100644 --- a/src/you.mo +++ b/src/you.mo @@ -31,6 +31,8 @@ module { reboot_board_write : (name : Name, mood : Mood) -> async Result.Result<(), Service.WriteError>; }; + public let init = Migration.migrate; +