diff --git a/src/backend/Comments.mo b/src/backend/Comments.mo index 93c7e19..24f2b02 100644 --- a/src/backend/Comments.mo +++ b/src/backend/Comments.mo @@ -2,227 +2,257 @@ import Nat "mo:base/Nat"; import Time "mo:base/Time"; import List "mo:base/List"; import Error "mo:base/Error"; +import Principal "mo:base/Principal"; +import Array "mo:base/Array"; import Types "Types"; import Constants "Constants"; import { - validateComment; - hashComment; - userToQueryUser; - fundsAvalaible; + validateComment; + hashComment; + userToQueryUser; + fundsAvalaible; + isAdmin; } "Utils"; module { - type State = Types.State; - type Users = Types.Users; + type State = Types.State; + type Users = Types.Users; - type User = Types.User; + type User = Types.User; - type Comment = Types.Comment; - type CommentHash = Types.CommentHash; + type Comment = Types.Comment; + type CommentHash = Types.CommentHash; - type PostResult = Types.PostResult; - type LikeResult = Types.LikeResult; + type PostResult = Types.PostResult; + type LikeResult = Types.LikeResult; - type QueryComment = Types.QueryComment; - type QueryUser = Types.QueryUser; + type QueryComment = Types.QueryComment; + type QueryUser = Types.QueryUser; - // Constants - let COMMENT_REWARD = Constants.COMMENT_REWARD; - let LIKE_REWARD = Constants.LIKE_REWARD; + // Constants + let COMMENT_REWARD = Constants.COMMENT_REWARD; + let LIKE_REWARD = Constants.LIKE_REWARD; - let COMMENT_INTERVAL = Constants.COMMENT_INTERVAL; - let LIKE_INTERVAL = Constants.LIKE_INTERVAL; - // PUBLIC METHOD IMPLEMENTATIONS + let COMMENT_INTERVAL = Constants.COMMENT_INTERVAL; + let LIKE_INTERVAL = Constants.LIKE_INTERVAL; - public func register(users : Users, principal : Principal) : QueryUser { - switch (users.get(principal)) { + // PUBLIC METHOD IMPLEMENTATIONS - // If user doesn't exist, create a new one - case (null) { - let newUser : User = { - id = users.size() + 1; - balance = 0; - lastPost = 0; - lastLike = 0; - likes = List.nil(); - }; + public func register(users : Users, principal : Principal) : QueryUser { + switch (users.get(principal)) { - users.put(principal, newUser); + // If user doesn't exist, create a new one + case (null) { + let newUser : User = { + id = users.size() + 1; + balance = 0; + lastPost = 0; + lastLike = 0; + likes = List.nil(); + }; - userToQueryUser(newUser); - }; + users.put(principal, newUser); - // If user exists, return it - case (?user) { userToQueryUser(user) }; - }; + userToQueryUser(newUser); + }; + + // If user exists, return it + case (?user) { userToQueryUser(user) }; }; + }; - public func postComment(state : State, owner : Principal, comment : Text) : PostResult { - // Check if comment is valid - if (not validateComment(comment)) return #err(#InvalidComment); + public func postComment(state : State, owner : Principal, comment : Text) : PostResult { + // Check if comment is valid + if (not validateComment(comment)) return #err(#InvalidComment); - switch (state.users.get(owner)) { + switch (state.users.get(owner)) { - // Users must be registered before posting - // If user doesn't exist, return error - case (null) { #err(#UserNotFound) }; + // Users must be registered before posting + // If user doesn't exist, return error + case (null) { #err(#UserNotFound) }; - // If user exists, post comment - case (?user) { - let now = Time.now(); + // If user exists, post comment + case (?user) { + let now = Time.now(); - // Check if user has posted recently - if (now - user.lastPost < COMMENT_INTERVAL) { - return #err(#TimeRemaining(COMMENT_INTERVAL - (now - user.lastPost))); - }; + // Check if user has posted recently + if (now - user.lastPost < COMMENT_INTERVAL) { + return #err(#TimeRemaining(COMMENT_INTERVAL - (now - user.lastPost))); + }; - // Create new comment record - let postComment : Comment = { - created = now; - owner; - comment; - reward = 0; - }; - let hash = hashComment(postComment); + // Create new comment record + let postComment : Comment = { + created = now; + owner; + comment; + reward = 0; + }; + + let hash = hashComment(postComment); + + // If treasury is not empty, subtract and add an equal amount of funds + var reward = 0; + if (fundsAvalaible(state.treasury, COMMENT_REWARD)) { + state.treasury -= COMMENT_REWARD; + reward += COMMENT_REWARD; + }; - // If treasury is not empty, subtract and add an equal amount of funds - var reward = 0; - if (fundsAvalaible(state.treasury, COMMENT_REWARD)) { - state.treasury -= COMMENT_REWARD; - reward += COMMENT_REWARD; - }; - - let balance = user.balance + reward; - - // Create new user record - let newUser : User = { - user with - balance; - lastPost = now; - likes = List.make(hash); - }; + let balance = user.balance + reward; - // Update state within atomic block after all checks have passed - state.users.put(owner, newUser); + // Create new user record + let newUser : User = { + user with + balance; + lastPost = now; + // comment owner can also like its comment now, because not updating its like list with current post + }; + - state.commentStore.put(hash, postComment); + // Update state within atomic block after all checks have passed + state.users.put(owner, newUser); - state.commentHistory := List.push(hash, state.commentHistory); + state.commentStore.put(hash, postComment); - #ok(); - }; - }; + state.commentHistory := List.push(hash, state.commentHistory); + + #ok(); + }; }; + }; - public func likeComment(state : State, hash : CommentHash, liker : Principal) : async* LikeResult { - // Like the comment if possible - switch (state.users.get(liker)) { + public func likeComment(state : State, hash : CommentHash, liker : Principal) : async* LikeResult { + // Like the comment if possible + switch (state.users.get(liker)) { - // Users must be registered before liking - case (null) { return #err(#UserNotFound) }; + // Users must be registered before liking + case (null) { return #err(#UserNotFound) }; - // If user exists, like comment - case (?user) { - let now = Time.now(); + // If user exists, like comment + case (?user) { + let now = Time.now(); - // Check if user has liked this comment before - if (List.some(user.likes, func(h : CommentHash) : Bool { h == hash })) { - return #err(#AlreadyLiked); - }; + // Check if user has liked this comment before + if (List.some(user.likes, func(h : CommentHash) : Bool { h == hash })) { + return #err(#AlreadyLiked); + }; - // Check if user has liked recently - if (now - user.lastLike < LIKE_INTERVAL) { - return #err(#TimeRemaining(LIKE_INTERVAL - (now - user.lastLike))); - }; + // Check if user has liked recently + if (now - user.lastLike < LIKE_INTERVAL) { + return #err(#TimeRemaining(LIKE_INTERVAL - (now - user.lastLike))); + }; - // Add comment to user's liked comments list - let newUser : User = { - user with - lastLike = now; - lastPost = now; - likes = List.push(hash, user.likes); - }; + // Add comment to user's liked comments list + let newUser : User = { + user with + lastLike = now; + lastPost = now; + likes = List.push(hash, user.likes); + }; - // Update state within atomic block after all checks have passed - state.users.put(liker, newUser); + // Update state within atomic block after all checks have passed + state.users.put(liker, newUser); + }; + }; + + // Update comment reward and user balance + switch (state.commentStore.get(hash)) { + + // If comment doesn't exist, return error + // Error is thrown before any state updates + case (null) throw Error.reject("Comment not found"); + + // If comment exists, update reward and balance + case (?comment) { + switch (state.users.get(comment.owner)) { + case (null) { + throw Error.reject("Comment has no owner"); + }; + case (?owner) { + // If treasury is not empty, subtract and add to user balance and comment total reward + var likeReward = 0; + if (fundsAvalaible(state.treasury, LIKE_REWARD)) { + state.treasury -= LIKE_REWARD; + likeReward += LIKE_REWARD; }; - }; + let reward = comment.reward + likeReward; + let balance = owner.balance + likeReward; - // Update comment reward and user balance - switch (state.commentStore.get(hash)) { + // Create new comment and owner records + let newComment : Comment = { + comment with + reward; + }; + let newOwner : User = { + owner with + balance; + }; - // If comment doesn't exist, return error - // Error is thrown before any state updates - case (null) throw Error.reject("Comment not found"); - - // If comment exists, update reward and balance - case (?comment) { - switch (state.users.get(comment.owner)) { - case (null) { - throw Error.reject("Comment has no owner"); - }; - case (?owner) { - // If treasury is not empty, subtract and add to user balance and comment total reward - var likeReward = 0; - if (fundsAvalaible(state.treasury, LIKE_REWARD)) { - state.treasury -= LIKE_REWARD; - likeReward += LIKE_REWARD; - }; - let reward = comment.reward + likeReward; - let balance = owner.balance + likeReward; - - // Create new comment and owner records - let newComment : Comment = { - comment with - reward; - }; - let newOwner : User = { - owner with - balance; - }; - - // Update state within atomic block after all checks have passed - state.commentStore.put(hash, newComment); - state.users.put(comment.owner, newOwner); - - #ok(newComment.reward); - }; + // Update state within atomic block after all checks have passed + state.commentStore.put(hash, newComment); + state.users.put(comment.owner, newOwner); + #ok(newComment.reward); + }; + + }; + }; + }; + }; + + public func latestComments(state : State) : [QueryComment] { + // Take the latest 50 comment hashes + let latestHashes = List.take(state.commentHistory, 50); + + // Map comment hashes to [QueryComment] + let comments = List.mapFilter( + latestHashes, + func(hash : CommentHash) : ?QueryComment { + switch (state.commentStore.get(hash)) { + case (null) return null; + case (?comment) { + switch (state.users.get(comment.owner)) { + case (null) return null; + case (?user) { + let userId = "User" # Nat.toText(user.id); + ?{ + created = comment.created; + userId; + reward = comment.reward; + comment = comment.comment; + hash; }; + }; }; + }; }; + }, + ); + List.toArray(comments); + }; + + public func deleteComment(state : State, owner : Principal, commentHash : CommentHash) : async* () { + + // Check if user is an admin + if (not isAdmin(owner)) { + throw Error.reject("Not Admin"); }; - public func latestComments(state : State) : [QueryComment] { - // Take the latest 50 comment hashes - let latestHashes = List.take(state.commentHistory, 50); - - // Map comment hashes to [QueryComment] - let comments = List.mapFilter( - latestHashes, - func(hash : CommentHash) : ?QueryComment { - switch (state.commentStore.get(hash)) { - case (null) return null; - case (?comment) { - switch (state.users.get(comment.owner)) { - case (null) return null; - case (?user) { - let userId = "User" # Nat.toText(user.id); - ?{ - created = comment.created; - userId; - reward = comment.reward; - comment = comment.comment; - hash; - }; - }; - }; - }; - }; - }, - ); - List.toArray(comments); + // Check if the comment exists + switch (state.commentStore.get(commentHash)) { + case (null) { throw Error.reject("CommentNotFound") }; + case (?comment) { + func check(arg : CommentHash) : Bool { + not (arg == commentHash); + }; + // Delete the comment from the commentStore and update relevant data structures + state.commentHistory := List.filter(state.commentHistory, check); + state.commentStore.delete(commentHash); + return (); + }; }; + + }; + }; diff --git a/src/backend/Constants.mo b/src/backend/Constants.mo index da4efd5..1308119 100644 --- a/src/backend/Constants.mo +++ b/src/backend/Constants.mo @@ -8,4 +8,9 @@ module { public let MIN_COMMENT_SIZE = 3; public let MAX_COMMENT_SIZE = 200; + + public let ADMIN_PRINCIPALS = [ + "2y4af-2y6bs-xh7ta-twhrl-u4dxa-ldih3-uqzqb-7ttid-n2ub3-rzofp-uae", + "be2us-64aaa-aaaaa-qaabq-cai" + ] } \ No newline at end of file diff --git a/src/backend/Types.mo b/src/backend/Types.mo index 7185391..b448334 100644 --- a/src/backend/Types.mo +++ b/src/backend/Types.mo @@ -47,6 +47,7 @@ module { public type LikeResult = Result.Result; type LikeError = Error or { # AlreadyLiked }; + // Queries public type QueryComment = { diff --git a/src/backend/Utils.mo b/src/backend/Utils.mo index d9d1ee7..26601ea 100644 --- a/src/backend/Utils.mo +++ b/src/backend/Utils.mo @@ -3,6 +3,7 @@ import Principal "mo:base/Principal"; import Int "mo:base/Int"; import Text "mo:base/Text"; import List "mo:base/List"; +import Array "mo:base/Array"; import Types "Types"; import Constants "Constants"; @@ -54,4 +55,17 @@ module { public func fundsAvalaible(t : Treasury, amount : Nat) : Bool { t >= amount; }; + + // Check if user is an admin + public func isAdmin(p : Principal) : Bool { + let optVal : ?Text = Array.find(Constants.ADMIN_PRINCIPALS, func (t) : Bool { + t == Principal.toText(p) + }); + + switch(optVal) { + case(?t) { true }; + case(null) { false}; + }; + }; + }; diff --git a/src/backend/main.mo b/src/backend/main.mo index fc65084..4a3b4b2 100644 --- a/src/backend/main.mo +++ b/src/backend/main.mo @@ -90,6 +90,10 @@ actor { await* Comments.likeComment(state, hash, msg.caller); }; + public shared (msg) func deleteComment(hash : CommentHash) : async () { + await* Comments.deleteComment(state, msg.caller, hash); + }; + public query func latestComments() : async [QueryComment] { // Anonymous users can query comments Comments.latestComments(state);