From 595cdd574cb6e2a4b833d7dfc8b6d3c632ab5087 Mon Sep 17 00:00:00 2001 From: xiaoyuanxun <53613219+xiaoyuanxun@users.noreply.github.com> Date: Wed, 22 Nov 2023 17:11:04 +0800 Subject: [PATCH] update --- dfx.json | 32 +++++++- src/feed/database.mo | 150 ++++++++++++++++++++++++-------------- src/feed/feed.mo | 142 ++++++++++++++++++++++++++++++++++++ src/feed/main.mo | 117 ----------------------------- src/feed/rootFeed.mo | 55 ++++++++++++++ src/feed/types.mo | 74 ++++++------------- src/fetch/commentFetch.mo | 4 + src/fetch/likeFetch.mo | 4 + src/fetch/postFetch.mo | 4 + src/fetch/rootFetch.mo | 4 + src/post/bucket.mo | 91 +++++++++++++++++++++++ src/post/main.mo | 122 ------------------------------- src/post/post.mo | 89 ++++++++++++++++++++++ src/post/rootPost.mo | 120 ++++++++++++++++++++++++++++++ src/post/types.mo | 19 +++-- src/types.mo | 86 ++++++++++++++++++++++ src/user/database.mo | 16 ++-- src/user/main.mo | 15 ++-- src/user/types.mo | 22 +++--- src/user/utils.mo | 45 ------------ test/feed.test.mo | 40 +++++++++- 21 files changed, 817 insertions(+), 434 deletions(-) create mode 100644 src/feed/feed.mo delete mode 100644 src/feed/main.mo create mode 100644 src/feed/rootFeed.mo create mode 100644 src/fetch/commentFetch.mo create mode 100644 src/fetch/likeFetch.mo create mode 100644 src/fetch/postFetch.mo create mode 100644 src/fetch/rootFetch.mo create mode 100644 src/post/bucket.mo delete mode 100644 src/post/main.mo create mode 100644 src/post/post.mo create mode 100644 src/post/rootPost.mo create mode 100644 src/types.mo delete mode 100644 src/user/utils.mo diff --git a/dfx.json b/dfx.json index 2915c4b..1a57234 100644 --- a/dfx.json +++ b/dfx.json @@ -6,11 +6,39 @@ }, "post": { "type": "motoko", - "main": "src/post/main.mo" + "main": "src/post/post.mo" + }, + "rootPost": { + "type": "motoko", + "main": "src/post/rootPost.mo" }, "feed": { "type": "motoko", - "main": "src/feed/main.mo" + "main": "src/feed/feed.mo" + }, + "rootFeed": { + "type": "motoko", + "main": "src/feed/rootFeed.mo" + }, + "bucket": { + "type": "motoko", + "main": "src/post/bucket.mo" + }, + "rootFetch": { + "type": "motoko", + "main": "src/fetch/rootFetch.mo" + }, + "postFetch": { + "type": "motoko", + "main": "src/fetch/postFetch.mo" + }, + "commentFetch": { + "type": "motoko", + "main": "src/fetch/commentFetch.mo" + }, + "likeFetch": { + "type": "motoko", + "main": "src/fetch/likeFetch.mo" } }, "defaults": { diff --git a/src/feed/database.mo b/src/feed/database.mo index deff196..a5447f8 100644 --- a/src/feed/database.mo +++ b/src/feed/database.mo @@ -22,62 +22,104 @@ module { type Like = Types.Like; var postIndex: Nat = 0; - let postMap = TrieMap.TrieMap(Nat.equal, Hash.hash); + let postMap = TrieMap.TrieMap(Nat.equal, Hash.hash); // postIndex -> Post + + private func _getPostId(bucket: Principal, user: Principal, index: Nat): Text { + Principal.toText(bucket) # "#" # Principal.toText(user) # "#" # Nat.toText(index) + }; // 发帖 - public func createPost(user: UserId, title: Text, content: Text, time: Time) { - postMap.put(postIndex, { + public func createPost(user: UserId, title: Text, content: Text, time: Time, bucket: Principal): PostImmutable { + let post: Post = { + postId = _getPostId(bucket, user, postIndex); index = postIndex; user = user; + repost = []; title = title; content = content; var like = []; - var likeIndex = 0; var comment = []; var commentIndex = 0; createdAt = time; - }); - postIndex += 1; + }; + + postMap.put(postIndex, post); + + _convertPostToImmutable(post) }; - // 删帖 - public func deletePost(postIndex: Nat) { - postMap.delete(postIndex); + private func _convertPostToImmutable(post: Post): PostImmutable { + { + postId = post.postId; + index = post.index; + user = post.user; + repost = post.repost; + title = post.title; + content = post.content; + like = post.like; + comment = post.comment; + commentIndex = post.commentIndex; + createdAt = post.createdAt; + } }; + // public func getPost(postIndex: Nat): ?PostImmutable { + // switch(postMap.get(postIndex)) { + // case(null) {}; + // case(?post) { + // return ?{ + // index = post.index; + // user = post.user; + // title = post.title; + // content = post.content; + // like = post.like; + // likeNumber = Array.size(post.like); + // comment = post.comment; + // commentNumber = Array.size(post.comment); + // commentIndex = post.commentIndex; + // createdAt = post.createdAt; + // } + // }; + // }; + // null + // }; + // 查询所有帖子 - public func getPosts(): [PostImmutable] { - var ans: [PostImmutable] = []; - for(post in postMap.vals()) { - ans := Array.append(ans, [{ - index = post.index; - user = post.user; - title = post.title; - content = post.content; - like = post.like; - comment = post.comment; - commentIndex = post.commentIndex; - createdAt = post.createdAt; - }]); - }; - ans - }; + // public func getPosts(): [PostImmutable] { + // var ans: [PostImmutable] = []; + // for(post in postMap.vals()) { + // ans := Array.append(ans, [{ + // index = post.index; + // user = post.user; + // title = post.title; + // content = post.content; + // like = post.like; + // likeNumber = Array.size(post.like); + // comment = post.comment; + // commentNumber = Array.size(post.comment); + // commentIndex = post.commentIndex; + // createdAt = post.createdAt; + // }]); + // }; + // ans + // }; // 评论 - public func createComment(commentUser: UserId, postIndex: Nat, content: Text, createdAt: Time) { - switch(postMap.get(postIndex)) { - case(null) {}; - case(?post) { - post.comment := Array.append(post.comment, [{ - index = post.commentIndex; - user = commentUser; - content = content; - createdAt = createdAt; - }]); - post.commentIndex += 1; - }; - }; - }; + // public func createComment(commentUser: UserId, postIndex: Nat, content: Text, createdAt: Time): ?Nat { + // switch(postMap.get(postIndex)) { + // case(null) { return null;}; + // case(?post) { + // post.comment := Array.append(post.comment, [{ + // index = post.commentIndex; + // user = commentUser; + // content = content; + // createdAt = createdAt; + // }]); + // post.commentIndex += 1; + // return ?(post.commentIndex - 1); + // }; + // }; + // }; // 删评 public func deleteComment(commentUser: UserId, postIndex: Nat, commentIndex: Nat) { @@ -108,21 +150,21 @@ module { }; // 点赞 - public func createLike(likeUser: UserId, postIndex: Nat, createdAt: Time) { - switch(postMap.get(postIndex)) { - case(null) {}; - case(?post) { - for(like in post.like.vals()) { - // 已经点赞过 - if(like.user == likeUser) { return;}; - }; - post.like := Array.append(post.like, [{ - user = likeUser; - createdAt = createdAt; - }]); - }; - } - }; + // public func createLike(likeUser: UserId, postIndex: Nat, createdAt: Time) { + // switch(postMap.get(postIndex)) { + // case(null) {}; + // case(?post) { + // for(like in post.like.vals()) { + // // 已经点赞过 + // if(like.user == likeUser) { return;}; + // }; + // post.like := Array.append(post.like, [{ + // user = likeUser; + // createdAt = createdAt; + // }]); + // }; + // } + // }; // 取点 public func deleteLike(likeUser: UserId, postIndex: Nat) { diff --git a/src/feed/feed.mo b/src/feed/feed.mo new file mode 100644 index 0000000..363f3b7 --- /dev/null +++ b/src/feed/feed.mo @@ -0,0 +1,142 @@ +import Database "./database"; +import Types "./types"; +import Principal "mo:base/Principal"; +import TrieMap "mo:base/TrieMap"; +import Array "mo:base/Array"; +import Order "mo:base/Order"; +import Time "mo:base/Time"; +import Debug "mo:base/Debug"; +import Option "mo:base/Option"; +import Bucket "../post/bucket"; + +actor class Feed( + _owner: Principal, + rootPostCanister: Principal, + userCanister: Principal +) = this { + + stable var owner = _owner; + + public query func getOwner(): async Principal { owner }; + + public shared({caller}) func updateOwner(newOwner: Principal): async () { + assert(caller == owner); + owner := newOwner; + }; + + public query({caller}) func whoami(): async Principal { caller }; + +// Bucket + + type RootPostActor = Types.RootPostActor; + stable var bucket: ?Principal = null; + let rootPostActor: RootPostActor = actor(Principal.toText(rootPostCanister)); + + // 更新当前 feed 去存储的 bucket canister + public shared func checkAvailableBucket(): async Bool { + switch((await rootPostActor.getAvailableBucket())) { + case(null) { return false; }; + case(?bucketInfo) { + bucket := ?bucketInfo.canisterId; + return true; + }; + }; + }; + +// Post + + type Time = Types.Time; + type UserId = Types.UserId; + type BucketActor = Types.BucketActor; + + let postDirectory: Database.PostDirectory = Database.PostDirectory(); + + public shared({caller}) func createPost(title: Text, content: Text): async Bool { + assert(caller == owner and bucket != null); + let _bucket = Option.unwrap(bucket); + let post: PostImmutable = postDirectory.createPost(caller, title, content, Time.now(), _bucket); + + // 将帖子内容发送给公共区的 Bucket + let bucketActor: BucketActor = actor(Principal.toText(_bucket)); + ignore bucketActor.storeFeed(post); + + // 把发帖人、帖子 ID 、用户 C 、D(followers)发送给 Fetch + true + }; + + // public query func getPost(postIndex: Nat): async ?PostImmutable { + // postDirectory.getPost(postIndex) + // }; + + // public query func getPosts(): async [PostImmutable] { + // postDirectory.getPosts() + // }; + + // public shared({caller}) func createComment(commentUser: UserId, postIndex: Nat, content: Text): async ?Nat { + // assert(caller == postCanister or caller == owner); + // let commentIndex = postDirectory.createComment(commentUser, postIndex, content, Time.now()); + // await sendFeed(); + // commentIndex + // }; + + // public shared({caller}) func deleteComment(commentUser: UserId, postIndex: Nat, commentIndex: Nat): async () { + // assert(caller == rootPostCanister); + // postDirectory.deleteComment(commentUser, postIndex, commentIndex); + // await sendFeed(); + // }; + + // public shared({caller}) func createLike(likeUser: UserId, postIndex: Nat): async () { + // assert(caller == postCanister); + // postDirectory.createLike(likeUser, postIndex, Time.now()); + // await sendFeed(); + // }; + + // public shared({caller}) func deleteLike(likeUser: UserId, postIndex: Nat) { + // assert(caller == rootPostCanister); + // postDirectory.deleteLike(likeUser, postIndex); + // await sendFeed(); + // }; + +// Feed + + type PostImmutable = Types.PostImmutable; + type FeedActor = Types.FeedActor; + type UserActor = Types.UserActor; + + // let postActor: PostActor = actor(Principal.toText(rootPostCanister)); + let feedMap = TrieMap.TrieMap(Principal.equal, Principal.hash); + + // public shared({caller}) func sendFeed(): async () { + // assert(caller == owner or caller == Principal.fromActor(this)); + // let userActor: UserActor = actor(Principal.toText(userCanister)); + // Debug.print("userCanister : " # Principal.toText(userCanister)); + // let followersList = await userActor.getFollowersList(owner); + // for(user in followersList.vals()) { + // let feedActor: FeedActor = actor(Principal.toText(user)); + // await feedActor.receiveFeed(); + // }; + // await postActor.receiveFeed(); + // }; + + public shared({caller}) func receiveFeed(): async () { + let feedActor: FeedActor = actor(Principal.toText(caller)); + let posts = await feedActor.getPosts(); + feedMap.put(caller, posts); + }; + + public query({caller}) func getFeed(): async [PostImmutable] { + var ans: [PostImmutable] = []; + for(posts in feedMap.vals()) { + ans := Array.append(ans, posts); + }; + Array.sort( + ans, + func (x: PostImmutable, y: PostImmutable): Order.Order { + if(x.createdAt > y.createdAt) return #less + else if(x.createdAt < y.createdAt) return #greater + else return #equal + } + ) + }; + +} \ No newline at end of file diff --git a/src/feed/main.mo b/src/feed/main.mo deleted file mode 100644 index 6907d77..0000000 --- a/src/feed/main.mo +++ /dev/null @@ -1,117 +0,0 @@ -import Database "./database"; -import Types "./types"; -import Principal "mo:base/Principal"; -import TrieMap "mo:base/TrieMap"; -import Array "mo:base/Array"; -import Order "mo:base/Order"; -import Time "mo:base/Time"; -import Debug "mo:base/Debug"; - -actor class Feed( - _owner: Principal, - postCanister: Principal, - userCanister: Principal -) = this { - - stable var owner = _owner; - - public query func getOwner(): async Principal { owner }; - - public shared({caller}) func updateOwner(newOwner: Principal): async () { - assert(caller == owner); - owner := newOwner; - }; - - public query({caller}) func whoami(): async Principal { caller }; - -// Post - - type Time = Types.Time; - type UserId = Types.UserId; - - let postDirectory: Database.PostDirectory = Database.PostDirectory(); - - public shared({caller}) func createPost(title: Text, content: Text): async () { - assert(caller == owner); - postDirectory.createPost(caller, title, content, Time.now()); - await sendFeed(); - }; - - public shared({caller}) func deletePost(postIndex: Nat): async () { - assert(caller == owner); - postDirectory.deletePost(postIndex); - await sendFeed(); - }; - - public query func getPosts(): async [PostImmutable] { - postDirectory.getPosts() - }; - - public shared({caller}) func createComment(commentUser: UserId, postIndex: Nat, content: Text): async () { - assert(caller == postCanister); - postDirectory.createComment(commentUser, postIndex, content, Time.now()); - await sendFeed(); - }; - - public shared({caller}) func deleteComment(commentUser: UserId, postIndex: Nat, commentIndex: Nat): async () { - assert(caller == postCanister); - postDirectory.deleteComment(commentUser, postIndex, commentIndex); - await sendFeed(); - }; - - public shared({caller}) func createLike(likeUser: UserId, postIndex: Nat): async () { - assert(caller == postCanister); - postDirectory.createLike(likeUser, postIndex, Time.now()); - await sendFeed(); - }; - - public shared({caller}) func deleteLike(likeUser: UserId, postIndex: Nat) { - assert(caller == postCanister); - postDirectory.deleteLike(likeUser, postIndex); - await sendFeed(); - }; - -// Feed - - type PostImmutable = Types.PostImmutable; - type PostActor = Types.PostActor; - type FeedActor = Types.FeedActor; - type UserActor = Types.UserActor; - - let postActor: PostActor = actor(Principal.toText(postCanister)); - let feedMap = TrieMap.TrieMap(Principal.equal, Principal.hash); - - public shared({caller}) func sendFeed(): async () { - assert(caller == owner or caller == Principal.fromActor(this)); - let userActor: UserActor = actor(Principal.toText(userCanister)); - Debug.print("userCanister : " # Principal.toText(userCanister)); - let followersList = await userActor.getFollowersList(owner); - for(user in followersList.vals()) { - let feedActor: FeedActor = actor(Principal.toText(user)); - await feedActor.receiveFeed(); - }; - await postActor.receiveFeed(); - }; - - public shared({caller}) func receiveFeed(): async () { - let feedActor: FeedActor = actor(Principal.toText(caller)); - let posts = await feedActor.getPosts(); - feedMap.put(caller, posts); - }; - - public query({caller}) func getFeed(): async [PostImmutable] { - var ans: [PostImmutable] = []; - for(posts in feedMap.vals()) { - ans := Array.append(ans, posts); - }; - Array.sort( - ans, - func (x: PostImmutable, y: PostImmutable): Order.Order { - if(x.createdAt > y.createdAt) return #less - else if(x.createdAt < y.createdAt) return #greater - else return #equal - } - ) - }; - -} \ No newline at end of file diff --git a/src/feed/rootFeed.mo b/src/feed/rootFeed.mo new file mode 100644 index 0000000..5f0d6d2 --- /dev/null +++ b/src/feed/rootFeed.mo @@ -0,0 +1,55 @@ +import Types "./types"; +import IC "mo:ic"; +import TrieMap "mo:base/TrieMap"; +import Principal "mo:base/Principal"; +import Feed "./feed"; +import Iter "mo:base/Iter"; + +actor class RootFeed( + rootPostCanister: Principal, + userCanister: Principal +) = this { + + let userFeedCanisterMap = TrieMap.TrieMap(Principal.equal, Principal.hash); + let ic: IC.Service = actor("aaaaa-aa"); + + // 给用户创建一个用户自己的 Canister + public shared({caller}) func createFeedCanister(): async ?Principal { + assert(_getUserFeedCanister(caller) == null); + let feedCanister = await Feed.Feed(caller, rootPostCanister, userCanister); + let feedCanisterId = Principal.fromActor(feedCanister); + userFeedCanisterMap.put(caller, feedCanisterId); + await ic.update_settings({ + canister_id = feedCanisterId; + settings = { + freezing_threshold = null; + controllers = ?[Principal.fromActor(this), caller, feedCanisterId]; + memory_allocation = null; + compute_allocation = null; + } + }); + ?feedCanisterId + }; + + public query func getUserFeedCanister(user: Principal): async ?Principal { + _getUserFeedCanister(user) + }; + + // return [(user, feedCanister)] + public query func getAllUserFeedCanister(): async [(Principal, Principal)] { + Iter.toArray(userFeedCanisterMap.entries()) + }; + + // 总共创建了多少个 Canister + public query func getTotalUserFeedCanisterNumber(): async Nat { + userFeedCanisterMap.size() + }; + + private func _getUserFeedCanister(user: Principal): ?Principal { + switch(userFeedCanisterMap.get(user)) { + case(null) { return null;}; + case(?canister) { return ?canister;}; + }; + }; + +} \ No newline at end of file diff --git a/src/feed/types.mo b/src/feed/types.mo index 70b4279..0c7c9d4 100644 --- a/src/feed/types.mo +++ b/src/feed/types.mo @@ -1,58 +1,26 @@ import Principal "mo:base/Principal"; import Time "mo:base/Time"; -import UserTypes "../user/types"; +import Types "../types"; module { - public type UserId = Principal; - public type Time = Time.Time; + + public type Post = Types.Post; + + public type Comment = Types.Comment; - public type Post = { - index: Nat; // Post Index - user: UserId; - title: Text; - content: Text; - var like: [Like]; - var comment: [Comment]; - var commentIndex: Nat; - createdAt: Time; - }; - - public type PostImmutable = { - index: Nat; // Post Index - user: UserId; - title: Text; - content: Text; - like: [Like]; - comment: [Comment]; - commentIndex: Nat; - createdAt: Time; - }; - - public type Comment = { - index: Nat; // Comment Index - user: UserId; - content: Text; - createdAt: Time; - }; - - public type Like = { - user: UserId; - createdAt: Time; - }; - - public type FeedActor = actor { - getPosts : shared query () -> async [PostImmutable]; - receiveFeed : shared () -> async (); - createComment : shared (Principal, Nat, Text) -> async (); - deleteComment : shared (Principal, Nat, Nat) -> async (); - createLike : shared (Principal, Nat) -> async (); - deleteLike : shared (Principal, Nat) -> async (); - }; - - public type UserActor = UserTypes.UserActor; - - public type PostActor = actor { - receiveFeed : shared () -> async (); - }; - -}; + public type Like = Types.Like; + + public type RootPostActor = Types.RootPostActor; + + public type Time = Types.Time; + + public type UserId = Types.UserId; + + public type PostImmutable = Types.PostImmutable; + + public type FeedActor = Types.FeedActor; + + public type UserActor = Types.UserActor; + + public type BucketActor = Types.BucketActor; +} \ No newline at end of file diff --git a/src/fetch/commentFetch.mo b/src/fetch/commentFetch.mo new file mode 100644 index 0000000..b052bf8 --- /dev/null +++ b/src/fetch/commentFetch.mo @@ -0,0 +1,4 @@ + +actor class CommentFetch() = this { + +}; \ No newline at end of file diff --git a/src/fetch/likeFetch.mo b/src/fetch/likeFetch.mo new file mode 100644 index 0000000..6c79122 --- /dev/null +++ b/src/fetch/likeFetch.mo @@ -0,0 +1,4 @@ + +actor class LikeFetch() = this { + +}; \ No newline at end of file diff --git a/src/fetch/postFetch.mo b/src/fetch/postFetch.mo new file mode 100644 index 0000000..5aa6dde --- /dev/null +++ b/src/fetch/postFetch.mo @@ -0,0 +1,4 @@ + +actor class PostFetch() = this { + +}; \ No newline at end of file diff --git a/src/fetch/rootFetch.mo b/src/fetch/rootFetch.mo new file mode 100644 index 0000000..fddc0b4 --- /dev/null +++ b/src/fetch/rootFetch.mo @@ -0,0 +1,4 @@ + +actor class RootFetch() = this { + +}; \ No newline at end of file diff --git a/src/post/bucket.mo b/src/post/bucket.mo new file mode 100644 index 0000000..90cb178 --- /dev/null +++ b/src/post/bucket.mo @@ -0,0 +1,91 @@ +import Types "./types"; +import TrieMap "mo:base/TrieMap"; +import Principal "mo:base/Principal"; +import Array "mo:base/Array"; +import Order "mo:base/Order"; +import Text "mo:base/Text"; +import Hash "mo:base/Hash"; +import Nat "mo:base/Nat"; +import Iter "mo:base/Iter"; +import Debug "mo:base/Debug"; +import Option "mo:base/Option"; + +shared(msg) actor class Bucket() = this { + + stable let installer = msg.caller; + + // private func _feedMap_equal(a: (Principal, Nat), b: (Principal, Nat)): Bool { + // if(a.0 == b.0 and a.1 == b.1) return true; + // false + // }; + + // private func _feedMap_hash(x: (Principal, Nat)): Hash.Hash { + // Text.hash( + // "User : " # Principal.toText(x.0) # + // "PostIndex : " # Nat.toText(x.1) + // ) + // }; + + type FeedActor = Types.FeedActor; + type PostImmutable = Types.PostImmutable; + + // postId -> PostImmutable + let feedMap = TrieMap.TrieMap(Text.equal, Text.hash); + + private func checkPostId(postId: Text): (Principal, Principal, Nat) { + let words = Iter.toArray(Text.split(postId, #char '#')); + let bucket = Principal.fromText(words[0]); + let user = Principal.fromText(words[1]); + let postIndex = Option.unwrap(Nat.fromText(words[2])); + Debug.print("(bucket, user, index) : (" # words[0] # "," # words[1] # "," # words[2] # ")"); + (bucket, user, postIndex) + }; + + // 存储帖子 + public shared({caller}) func storeFeed(post: PostImmutable): async Bool { + _storeFeed(post) + }; + + public shared({caller}) func batchStoreFeed(posts: [PostImmutable]): async () { + for(post in posts.vals()) { + ignore _storeFeed(post); + }; + }; + + private func _storeFeed(post: PostImmutable): Bool { + ignore checkPostId(post.postId); + switch(feedMap.get(post.postId)) { + case(?_post) { + Debug.print("This post has been stored"); + return false; + }; + case(null) { + feedMap.put(post.postId, post); + return true; + }; + }; + }; + + // 查询共有多少个帖子 + + // 根据ID查询某几个帖子(可以传入 7 个 ID 一次性返回 7 个帖子的内容) + + // 查询最新的 n 个帖子 + + + // public query({caller}) func getFeed(): async [PostImmutable] { + // var ans: [PostImmutable] = []; + // for(posts in feedMap.vals()) { + // ans := Array.append(ans, posts); + // }; + // Array.sort( + // ans, + // func (x: PostImmutable, y: PostImmutable): Order.Order { + // if(x.createdAt > y.createdAt) return #less + // else if(x.createdAt < y.createdAt) return #greater + // else return #equal + // } + // ) + // }; + +}; diff --git a/src/post/main.mo b/src/post/main.mo deleted file mode 100644 index 378eabd..0000000 --- a/src/post/main.mo +++ /dev/null @@ -1,122 +0,0 @@ -import Types "./types"; -import TrieMap "mo:base/TrieMap"; -import Principal "mo:base/Principal"; -import Array "mo:base/Array"; -import Order "mo:base/Order"; -import Option "mo:base/Option"; -import Feed "../feed/main"; -import IC "mo:ic"; -import Prelude "mo:base/Prelude"; - -actor class Post( - userCanister: Principal -) = this { - -// Canister - - let userFeedCanisterMap = TrieMap.TrieMap(Principal.equal, Principal.hash); - let ic: IC.Service = actor("aaaaa-aa"); - - public query func getUserFeedCanister(user: Principal): async ?Principal { - _getUserFeedCanister(user) - }; - - public shared({caller}) func createFeedCanister(): async ?Principal { - assert(_getUserFeedCanister(caller) == null); - let feedCanister = await Feed.Feed(caller, Principal.fromActor(this), userCanister); - let feedCanisterId = Principal.fromActor(feedCanister); - userFeedCanisterMap.put(caller, feedCanisterId); - await ic.update_settings({ - canister_id = feedCanisterId; - settings = { - freezing_threshold = null; - controllers = ?[Principal.fromActor(this), caller, feedCanisterId]; - memory_allocation = null; - compute_allocation = null; - } - }); - ?feedCanisterId - }; - - private func _getUserFeedCanister(user: Principal): ?Principal { - switch(userFeedCanisterMap.get(user)) { - case(null) { return null;}; - case(?canister) { return ?canister;}; - }; - }; - -// Feed - - type FeedActor = Types.FeedActor; - type PostImmutable = Types.PostImmutable; - - let feedMap = TrieMap.TrieMap(Principal.equal, Principal.hash); - - public shared({caller}) func receiveFeed(): async () { - let feedActor: FeedActor = actor(Principal.toText(caller)); - let posts = await feedActor.getPosts(); - feedMap.put(caller, posts); - }; - - public query({caller}) func getFeed(): async [PostImmutable] { - var ans: [PostImmutable] = []; - for(posts in feedMap.vals()) { - ans := Array.append(ans, posts); - }; - Array.sort( - ans, - func (x: PostImmutable, y: PostImmutable): Order.Order { - if(x.createdAt > y.createdAt) return #less - else if(x.createdAt < y.createdAt) return #greater - else return #equal - } - ) - }; - -// Comment - - type UserId = Types.UserId; - - public shared({caller}) func createComment(postUser: UserId, postIndex: Nat, content: Text): async () { - switch(_getUserFeedCanister(postUser)) { - case(null) {}; - case(?canisterId) { - let feedActor: FeedActor = actor(Principal.toText(canisterId)); - await feedActor.createComment(caller, postIndex, content); - }; - }; - }; - - public shared({caller}) func deleteComment(postUser: UserId, postIndex: Nat, commentIndex: Nat): async () { - switch(_getUserFeedCanister(postUser)) { - case(null) {}; - case(?canisterId) { - let feedActor: FeedActor = actor(Principal.toText(canisterId)); - await feedActor.deleteComment(caller, postIndex, commentIndex); - }; - }; - }; - -// Like - - public shared({caller}) func createLike(postUser: Principal, postIndex: Nat): async () { - switch(_getUserFeedCanister(postUser)) { - case(null) {}; - case(?canisterId) { - let feedActor: FeedActor = actor(Principal.toText(canisterId)); - await feedActor.createLike(caller, postIndex); - }; - }; - }; - - public shared({caller}) func deleteLike(postUser: Principal, postIndex: Nat): async () { - switch(_getUserFeedCanister(postUser)) { - case(null) {}; - case(?canisterId) { - let feedActor: FeedActor = actor(Principal.toText(canisterId)); - await feedActor.deleteLike(caller, postIndex); - }; - }; - }; - -} \ No newline at end of file diff --git a/src/post/post.mo b/src/post/post.mo new file mode 100644 index 0000000..6f9b965 --- /dev/null +++ b/src/post/post.mo @@ -0,0 +1,89 @@ +import Types "./types"; +import TrieMap "mo:base/TrieMap"; +import Principal "mo:base/Principal"; +import Array "mo:base/Array"; +import Order "mo:base/Order"; +import Option "mo:base/Option"; +import Feed "../feed/feed"; +import IC "mo:ic"; +import Prelude "mo:base/Prelude"; + +actor class Post( + userCanister: Principal +) = this { + +// Feed + +// type FeedActor = Types.FeedActor; +// type PostImmutable = Types.PostImmutable; + +// let feedMap = TrieMap.TrieMap(Principal.equal, Principal.hash); + +// public shared({caller}) func receiveFeed(): async () { +// let feedActor: FeedActor = actor(Principal.toText(caller)); +// let posts = await feedActor.getPosts(); +// feedMap.put(caller, posts); +// }; + +// public query({caller}) func getFeed(): async [PostImmutable] { +// var ans: [PostImmutable] = []; +// for(posts in feedMap.vals()) { +// ans := Array.append(ans, posts); +// }; +// Array.sort( +// ans, +// func (x: PostImmutable, y: PostImmutable): Order.Order { +// if(x.createdAt > y.createdAt) return #less +// else if(x.createdAt < y.createdAt) return #greater +// else return #equal +// } +// ) +// }; + +// // Comment + +// type UserId = Types.UserId; + +// public shared({caller}) func createComment(postUser: UserId, postIndex: Nat, content: Text): async () { +// switch(_getUserFeedCanister(postUser)) { +// case(null) {}; +// case(?canisterId) { +// let feedActor: FeedActor = actor(Principal.toText(canisterId)); +// await feedActor.createComment(caller, postIndex, content); +// }; +// }; +// }; + +// public shared({caller}) func deleteComment(postUser: UserId, postIndex: Nat, commentIndex: Nat): async () { +// switch(_getUserFeedCanister(postUser)) { +// case(null) {}; +// case(?canisterId) { +// let feedActor: FeedActor = actor(Principal.toText(canisterId)); +// await feedActor.deleteComment(caller, postIndex, commentIndex); +// }; +// }; +// }; + +// // Like + +// public shared({caller}) func createLike(postUser: Principal, postIndex: Nat): async () { +// switch(_getUserFeedCanister(postUser)) { +// case(null) {}; +// case(?canisterId) { +// let feedActor: FeedActor = actor(Principal.toText(canisterId)); +// await feedActor.createLike(caller, postIndex); +// }; +// }; +// }; + +// public shared({caller}) func deleteLike(postUser: Principal, postIndex: Nat): async () { +// switch(_getUserFeedCanister(postUser)) { +// case(null) {}; +// case(?canisterId) { +// let feedActor: FeedActor = actor(Principal.toText(canisterId)); +// await feedActor.deleteLike(caller, postIndex); +// }; +// }; +// }; + +} \ No newline at end of file diff --git a/src/post/rootPost.mo b/src/post/rootPost.mo new file mode 100644 index 0000000..087d6f5 --- /dev/null +++ b/src/post/rootPost.mo @@ -0,0 +1,120 @@ +import Hash "mo:base/Hash"; +import Nat "mo:base/Nat"; +import TrieMap "mo:base/TrieMap"; +import Principal "mo:base/Principal"; +import Types "./types"; +import Bucket "./bucket"; +import Iter "mo:base/Iter"; + +actor class RootPost() = this { + + type BucketInfo = Types.BucketInfo; + type BucketInfoImmutable = Types.BucketInfoImmutable; + + stable let BUCKET_MAX_POST_NUMBER: Nat = 5000; // 每个Bucket可以存储的最大帖子数 (待计算) + stable var bucketIndex: Nat = 0; + + let buckets = TrieMap.TrieMap(Nat.equal, Hash.hash); + let availableBuckets = TrieMap.TrieMap(Nat.equal, Hash.hash); + let unavailableBuckets = TrieMap.TrieMap(Nat.equal, Hash.hash); + + // 开始先创建 5 个 Bucket + public shared({caller}) func init(): async () { + var i = 0; + label l loop { + if(i >= 5) break l; + + let newBucket = await Bucket.Bucket(); + let bucketInfo: BucketInfo = { + index = bucketIndex; + canisterId = Principal.fromActor(newBucket); + var postNumber = 0; + }; + + buckets.put(bucketIndex, bucketInfo); + availableBuckets.put(bucketIndex, bucketInfo); + bucketIndex += 1; + + i += 1; + }; + }; + + // 创建Bucket + public shared({caller}) func createBucket(): async Principal { + let newBucket = await Bucket.Bucket(); + let bucketInfo: BucketInfo = { + index = bucketIndex; + canisterId = Principal.fromActor(newBucket); + var postNumber = 0; + }; + + buckets.put(bucketIndex, bucketInfo); + availableBuckets.put(bucketIndex, bucketInfo); + + bucketIndex += 1; + bucketInfo.canisterId + }; + + // 检查状态,如果存满则新建Bucket + public shared func checkBucket(): async () { + let avalBucketsVals = availableBuckets.vals(); + for(bucket in avalBucketsVals) { + if(bucket.postNumber >= BUCKET_MAX_POST_NUMBER) { + availableBuckets.delete(bucket.index); + unavailableBuckets.put(bucket.index, bucket); + ignore createBucket(); // 不等待结果 + }; + }; + }; + + // 查询可用的Bucket + public query func getAvailableBucket(): async ?BucketInfoImmutable { + for(bucket in availableBuckets.vals()) { + if(bucket.postNumber < BUCKET_MAX_POST_NUMBER) { + return ?{ + index = bucket.index; + canisterId = bucket.canisterId; + postNumber = bucket.postNumber; + }; + }; + }; + null + }; + + // 查询所有的Bucket + public query func getAllBuckets(): async [BucketInfoImmutable] { + Iter.toArray( + Iter.map( + buckets.vals(), + func (x: BucketInfo): BucketInfoImmutable { + { + index = x.index; + canisterId = x.canisterId; + postNumber = x.postNumber; + } + })) + }; + + // 查询已经存满的Bucket + public query func getUnavailableBuckets(): async [BucketInfoImmutable] { + Iter.toArray( + Iter.map( + unavailableBuckets.vals(), + func (x: BucketInfo): BucketInfoImmutable { + { + index = x.index; + canisterId = x.canisterId; + postNumber = x.postNumber; + } + })) + }; + + system func preupgrade() { + + }; + + system func postupgrade() { + + }; + +} \ No newline at end of file diff --git a/src/post/types.mo b/src/post/types.mo index c2d6682..e430143 100644 --- a/src/post/types.mo +++ b/src/post/types.mo @@ -1,15 +1,22 @@ -import FeedTypes "../feed/types"; +import Principal "mo:base/Principal"; +import Types "../types"; module { - public type PostImmutable = FeedTypes.PostImmutable; + public type PostImmutable = Types.PostImmutable; - public type FeedActor = FeedTypes.FeedActor; + public type FeedActor = Types.FeedActor; - public type UserId = FeedTypes.UserId; + public type UserId = Types.UserId; - public type PostActor = actor { - receiveFeed : shared () -> async (); + public type BucketInfo = { + index: Nat; + canisterId: Principal; + var postNumber: Nat; // 已经存储的帖子数量 }; + + public type BucketInfoImmutable = Types.BucketInfoImmutable; + public type RootPostActor = Types.RootPostActor + }; diff --git a/src/types.mo b/src/types.mo new file mode 100644 index 0000000..b42da3b --- /dev/null +++ b/src/types.mo @@ -0,0 +1,86 @@ +import Principal "mo:base/Principal"; +import Time "mo:base/Time"; + +module { +// Feed + + public type UserId = Principal; + public type Time = Time.Time; + public type PostId = Text; // 帖子 ID 是 Bucket Canister ID 加 UserId 加自增 + + public type Post = { + postId: PostId; // 帖子 ID + index: Nat; // Post Index + user: UserId; // 发布者 + repost: [UserId]; //转发者 + title: Text; + content: Text; + var like: [Like]; + var comment: [Comment]; + var commentIndex: Nat; + createdAt: Time; // 发布时间 + }; + + public type PostImmutable = { + postId: PostId; // 帖子 ID + index: Nat; // Post Index + user: UserId; // 发布者 + repost: [UserId]; //转发者 + title: Text; + content: Text; + like: [Like]; + comment: [Comment]; + commentIndex: Nat; + createdAt: Time; // 发布时间 + }; + + public type Comment = { + index: Nat; // Comment Index + user: UserId; + content: Text; + createdAt: Time; + }; + + public type Like = { + user: UserId; + createdAt: Time; + }; + + public type FeedActor = actor { + getPosts : shared query () -> async [PostImmutable]; + receiveFeed : shared () -> async (); + createComment : shared (Principal, Nat, Text) -> async (); + deleteComment : shared (Principal, Nat, Nat) -> async (); + createLike : shared (Principal, Nat) -> async (); + deleteLike : shared (Principal, Nat) -> async (); + }; + +// Post + + public type BucketInfoImmutable = { + index: Nat; + canisterId: Principal; + postNumber: Nat; // 已经存储的帖子数量 + }; + + public type RootPostActor = actor { + getAvailableBucket : shared query () -> async ?BucketInfoImmutable; + getAllBuckets : shared query () -> async [BucketInfoImmutable]; + getUnavailableBuckets : shared query () -> async [BucketInfoImmutable]; + }; + +// Bucket + + public type BucketActor = actor { + storeFeed : shared (PostImmutable) -> async Bool; + }; + +// User + + public type Vertex = Principal; + + public type UserActor = actor { + getFollowersList : shared query (Vertex) -> async [Vertex]; + }; + +} \ No newline at end of file diff --git a/src/user/database.mo b/src/user/database.mo index 4d847a0..d395b9b 100644 --- a/src/user/database.mo +++ b/src/user/database.mo @@ -18,15 +18,15 @@ module { type Time = Time.Time; public class Directory() { - // The "database" is just a local hash map + let hashMap = HashMap.HashMap(1, isEq, Principal.hash); public func createOne(userId: UserId, profile: NewProfile) { hashMap.put(userId, makeProfile(userId, profile)); }; - public func updateOne(userId: UserId, profile: Profile) { - hashMap.put(userId, profile); + public func updateOne(userId: UserId, profile: NewProfile) { + hashMap.put(userId, makeProfile(userId, profile)); }; public func findOne(userId: UserId): ?Profile { @@ -43,8 +43,7 @@ module { public func findBy(term: Text): [Profile] { var profiles: [Profile] = []; for ((id, profile) in hashMap.entries()) { - let fullName = profile.firstName # " " # profile.lastName; - if (includesText(fullName, term)) { + if (includesText(profile.name, term)) { profiles := Array.append(profiles, [profile]); }; }; @@ -56,13 +55,12 @@ module { func makeProfile(userId: UserId, profile: NewProfile): Profile { { id = userId; - firstName = profile.firstName; - lastName = profile.lastName; - title = profile.title; + name = profile.name; + biography = profile.biography; company = profile.company; - experience = profile.experience; education = profile.education; imgUrl = profile.imgUrl; + feedCanister = profile.feedCanister; } }; diff --git a/src/user/main.mo b/src/user/main.mo index c5ccd87..dcc37f5 100644 --- a/src/user/main.mo +++ b/src/user/main.mo @@ -1,7 +1,6 @@ import Digraph "./digraph"; import Types "./types"; import Database "./database"; -import Utils "./utils"; actor class User() = this { @@ -32,18 +31,16 @@ actor class User() = this { var directory: Database.Directory = Database.Directory(); - public shared(msg) func createProfile(profile: NewProfile): async () { - directory.createOne(msg.caller, profile); + public shared({caller}) func createProfile(profile: NewProfile): async () { + directory.createOne(caller, profile); }; - public shared(msg) func updateProfile(profile: Profile): async () { - if(Utils.hasAccess(msg.caller, profile)) { - directory.updateOne(profile.id, profile); - }; + public shared({caller}) func updateProfile(profile: NewProfile): async () { + directory.updateOne(caller, profile); }; - public query func getProfile(userId: UserId): async Profile { - Utils.getProfile(directory, userId) + public query func getProfile(userId: UserId): async ?Profile { + directory.findOne(userId) }; public query func searchProfile(term: Text): async [Profile] { diff --git a/src/user/types.mo b/src/user/types.mo index d78a2e8..430ed43 100644 --- a/src/user/types.mo +++ b/src/user/types.mo @@ -1,35 +1,31 @@ import Principal "mo:base/Principal"; import Time "mo:base/Time"; +import Types "../types"; module { - public type Vertex = Principal; + public type Vertex = Types.Vertex; public type UserId = Principal; public type Time = Time.Time; public type NewProfile = { - firstName: Text; - lastName: Text; - title: Text; + name: Text; + biography: Text; company: Text; - experience: Text; education: Text; imgUrl: Text; + feedCanister: ?Principal; }; public type Profile = { id: UserId; - firstName: Text; - lastName: Text; - title: Text; + name: Text; + biography: Text; company: Text; - experience: Text; education: Text; imgUrl: Text; + feedCanister: ?Principal; }; - - public type UserActor = actor { - getFollowersList : shared query (Vertex) -> async [Vertex]; - }; + public type UserActor = Types.UserActor; }; diff --git a/src/user/utils.mo b/src/user/utils.mo deleted file mode 100644 index 861996e..0000000 --- a/src/user/utils.mo +++ /dev/null @@ -1,45 +0,0 @@ -import Array "mo:base/Array"; -import Option "mo:base/Option"; -import Database "./database"; -import Types "./types"; - -module { - - type NewProfile = Types.NewProfile; - type Profile = Types.Profile; - type UserId = Types.UserId; - - // Profiles - public func getProfile(directory: Database.Directory, userId: UserId): Profile { - let existing = directory.findOne(userId); - switch (existing) { - case (?existing) { existing }; - case (null) { - { - id = userId; - firstName = ""; - lastName = ""; - title = ""; - company = ""; - experience = ""; - education = ""; - imgUrl = ""; - } - }; - }; - }; - - // Authorization - - let adminIds: [UserId] = []; - - public func isAdmin(userId: UserId): Bool { - func identity(x: UserId): Bool { x == userId }; - Option.isSome(Array.find(adminIds,identity)) - }; - - public func hasAccess(userId: UserId, profile: Profile): Bool { - userId == profile.id or isAdmin(userId) - }; - -}; diff --git a/test/feed.test.mo b/test/feed.test.mo index 4568553..6f8bb59 100644 --- a/test/feed.test.mo +++ b/test/feed.test.mo @@ -5,6 +5,11 @@ import Post "../src/post/main"; import User "../src/user/main"; import Feed "../src/feed/main"; import Debug "mo:base/Debug"; +import Option "mo:base/Option"; +import Nat "mo:base/Nat"; +import Array "mo:base/Array"; + +let testUser = Principal.fromText("wo5qg-ysjiq-5da"); let userActor = await User.User(); let userActorId = Principal.fromActor(userActor); @@ -21,7 +26,7 @@ Debug.print("postActor : " # Principal.toText(Principal.fromActor(postActor))); await asyncSuite("curd post test suite", func() : async () { let feedActor = await Feed.Feed( - Principal.fromText("wo5qg-ysjiq-5da"), + testUser, postActorId, userActorId ); @@ -32,14 +37,41 @@ await asyncSuite("curd post test suite", func() : async () { Debug.print("test identity : " # Principal.toText(caller)); }); + await asyncTest("create post test", func(): async () { + let index = await feedActor.createPost( + "title", + "content" + ); + Debug.print("create post index : " # Nat.toText(index)); + let result = await feedActor.getPost(index); + assert(result != null); + assert(Option.unwrap(result).index == index); + }); - await asyncTest("create post test", func() : async () { - let result = await feedActor.createPost( + await asyncTest("delete post test", func() : async () { + let index = await feedActor.createPost( "title", "content" ); - // let posts = await feedActor.getPosts(); + Debug.print("create post index : " # Nat.toText(index)); + assert((await feedActor.getPost(index)) != null); + Debug.print("delete post index 1"); + let result = await feedActor.deletePost(index); + assert(result == ()); + assert(Array.size((await feedActor.getPosts())) == 1); + }); + await asyncTest("create comment test", func(): async () { + Debug.print("user: " # Principal.toText(testUser) # "Comment on post index: 0"); + switch(await feedActor.createComment( + testUser, 0, "comment" + )) { + case(null) { assert(false); }; + case(?index) { + Debug.print("comment index : " # Nat.toText(index)); + assert(index == 0); + }; + } }); });