User.coffee 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. class User extends Class
  2. constructor: (row, @item_list) ->
  3. if row
  4. @setRow(row)
  5. @likes = {}
  6. @followed_users = {}
  7. @submitting_follow = false
  8. setRow: (row) ->
  9. @row = row
  10. @hub = row.hub
  11. @auth_address = row.auth_address
  12. get: (site, auth_address, cb=null) ->
  13. params = { site: site, directory: "data/users/"+auth_address }
  14. Page.cmd "dbQuery", ["SELECT * FROM json WHERE site = :site AND directory = :directory LIMIT 1", params], (res) =>
  15. row = res[0]
  16. if row
  17. if row.user_name == ""
  18. row.user_name = row.cert_user_id
  19. row.auth_address = row.directory.replace("data/users/", "")
  20. @setRow(row)
  21. cb?(row)
  22. else
  23. cb(false)
  24. updateInfo: (cb=null) =>
  25. @logStart "Info loaded"
  26. p_likes = new Promise()
  27. p_followed_users = new Promise()
  28. # Load followed users
  29. Page.cmd "dbQuery", ["SELECT * FROM follow WHERE json_id = #{@row.json_id}"], (res) =>
  30. @followed_users = {}
  31. for row in res
  32. @followed_users[row.hub+"/"+row.auth_address] = row
  33. p_followed_users.resolve()
  34. # Load likes
  35. Page.cmd "dbQuery", ["SELECT post_like.* FROM json LEFT JOIN post_like USING (json_id) WHERE directory = 'data/users/#{@auth_address}' AND post_uri IS NOT NULL"], (res) =>
  36. @likes = {}
  37. for row in res
  38. @likes[row.post_uri] = true
  39. p_likes.resolve()
  40. Promise.join(p_followed_users, p_likes).then (res1, res2) =>
  41. @logEnd "Info loaded"
  42. cb?(true)
  43. isFollowed: ->
  44. return Page.user.followed_users[@hub+"/"+@auth_address]
  45. isSeeding: ->
  46. return Page.merged_sites[@hub]
  47. hasHelp: (cb) =>
  48. Page.cmd "OptionalHelpList", [@hub], (helps) =>
  49. cb(helps["data/users/#{@auth_address}"])
  50. getPath: (site=@hub) ->
  51. if site == Page.userdb
  52. return "merged-ZeroMe/#{site}/data/userdb/#{@auth_address}"
  53. else
  54. return "merged-ZeroMe/#{site}/data/users/#{@auth_address}"
  55. getLink: ->
  56. return "?Profile/#{@hub}/#{@auth_address}/#{@row.cert_user_id or ''}"
  57. getAvatarLink: ->
  58. cache_invalidation = ""
  59. # Cache invalidation for local user
  60. if @auth_address == Page.user?.auth_address
  61. cache_invalidation = "?"+Page.cache_time
  62. return "merged-ZeroMe/#{@hub}/data/users/#{@auth_address}/avatar.#{@row.avatar}#{cache_invalidation}"
  63. getDefaultData: ->
  64. return {
  65. "next_post_id": 2,
  66. "next_comment_id": 1,
  67. "next_follow_id": 1,
  68. "avatar": "generate",
  69. "user_name": @row?.user_name,
  70. "hub": @hub,
  71. "intro": "Random ZeroNet user",
  72. "post": [{
  73. "post_id": 1,
  74. "date_added": Time.timestamp(),
  75. "body": "Hello ZeroMe!"
  76. }],
  77. "post_like": {},
  78. "comment": [],
  79. "follow": []
  80. }
  81. getData: (site, cb) ->
  82. Page.cmd "fileGet", [@getPath(site)+"/data.json", false], (data) =>
  83. data = JSON.parse(data)
  84. data ?= {
  85. "next_comment_id": 1,
  86. "user_name": @row?.user_name,
  87. "hub": @hub,
  88. "post_like": {},
  89. "comment": []
  90. }
  91. cb(data)
  92. renderAvatar: (attrs={}) =>
  93. if @isSeeding() and (@row.avatar == "png" or @row.avatar == "jpg")
  94. attrs.style = "background-image: url('#{@getAvatarLink()}')"
  95. else
  96. attrs.style = "background: linear-gradient("+Text.toColor(@auth_address)+","+Text.toColor(@auth_address.slice(-5))+")"
  97. h("a.avatar", attrs)
  98. save: (data, site=@hub, cb=null) ->
  99. Page.cmd "fileWrite", [@getPath(site)+"/data.json", Text.fileEncode(data)], (res_write) =>
  100. if Page.server_info.rev > 1400
  101. # Accidently left an unwanted modification in rev1400 fix
  102. Page.content.update()
  103. cb?(res_write)
  104. Page.cmd "sitePublish", {"inner_path": @getPath(site)+"/data.json"}, (res_sign) =>
  105. @log "Save result", res_write, res_sign
  106. if site == @hub and res_write == "ok" and res_sign == "ok"
  107. @saveUserdb(data)
  108. saveUserdb: (data, cb) ->
  109. cert_provider = Page.site_info.cert_user_id.replace(/.*@/, "")
  110. if cert_provider not in ["zeroid.bit", "zeroverse.bit"]
  111. @log "Cert provider #{cert_provider} not supported by userdb!"
  112. cb(false)
  113. return false
  114. Page.cmd "fileGet", [@getPath(Page.userdb)+"/content.json", false], (userdb_data) =>
  115. userdb_data = JSON.parse(userdb_data)
  116. changed = false
  117. if not userdb_data?.user
  118. userdb_data = {
  119. user: [{date_added: Time.timestamp()}]
  120. }
  121. changed = true
  122. for field in ["avatar", "hub", "intro", "user_name"]
  123. if userdb_data.user[0][field] != data[field]
  124. changed = true
  125. @log "Changed in profile:", field, userdb_data.user[0][field], "!=", data[field]
  126. userdb_data.user[0][field] = data[field]
  127. if changed
  128. Page.cmd "fileWrite", [@getPath(Page.userdb)+"/content.json", Text.fileEncode(userdb_data)], (res_write) =>
  129. Page.cmd "sitePublish", {"inner_path": @getPath(Page.userdb)+"/content.json"}, (res_sign) =>
  130. @log "Userdb save result", res_write, res_sign
  131. cb?(res_sign)
  132. like: (site, post_uri, cb=null) ->
  133. @log "Like", site, post_uri
  134. @likes[post_uri] = true
  135. @getData site, (data) =>
  136. data.post_like[post_uri] = Time.timestamp()
  137. @save data, site, (res) =>
  138. if cb then cb(res)
  139. dislike: (site, post_uri, cb=null) ->
  140. @log "Dislike", site, post_uri
  141. delete @likes[post_uri]
  142. @getData site, (data) =>
  143. delete data.post_like[post_uri]
  144. @save data, site, (res) =>
  145. if cb then cb(res)
  146. comment: (site, post_uri, body, cb=null) ->
  147. @getData site, (data) =>
  148. data.comment.push {
  149. "comment_id": data.next_comment_id,
  150. "body": body,
  151. "post_uri": post_uri,
  152. "date_added": Time.timestamp()
  153. }
  154. data.next_comment_id += 1
  155. @save data, site, (res) =>
  156. if cb then cb(res)
  157. # Add optional pattern to user's content.json
  158. checkContentJson: (cb=null) ->
  159. Page.cmd "fileGet", [@getPath(@hub)+"/content.json", false], (res) =>
  160. content_json = JSON.parse(res)
  161. if content_json.optional
  162. return cb(true)
  163. content_json.optional = "(?!avatar).*jpg"
  164. Page.cmd "fileWrite", [@getPath(@hub)+"/content.json", Text.fileEncode(content_json)], (res_write) =>
  165. cb(res_write)
  166. fileWrite: (file_name, content_base64, cb=null) ->
  167. if not content_base64
  168. return cb?(null)
  169. @checkContentJson =>
  170. Page.cmd "fileWrite", [@getPath(@hub)+"/"+file_name, content_base64], (res_write) =>
  171. cb?(res_write)
  172. post: (body, meta=null, image_base64=null, cb=null) ->
  173. @getData @hub, (data) =>
  174. post = {
  175. "post_id": Time.timestamp() + data.next_post_id,
  176. "body": body,
  177. "date_added": Time.timestamp()
  178. }
  179. if meta
  180. post["meta"] = Text.jsonEncode(meta)
  181. data.post.push post
  182. data.next_post_id += 1
  183. @fileWrite post.post_id+".jpg", image_base64, (res) =>
  184. @save data, @hub, (res) =>
  185. if cb then cb(res)
  186. followUser: (hub, auth_address, user_name, cb=null) ->
  187. @log "Following", hub, auth_address
  188. @download()
  189. @getData @hub, (data) =>
  190. follow_row = {
  191. "follow_id": data.next_follow_id,
  192. "hub": hub,
  193. "auth_address": auth_address,
  194. "user_name": user_name,
  195. "date_added": Time.timestamp()
  196. }
  197. data.follow.push follow_row
  198. @followed_users[hub+"/"+auth_address] = true
  199. data.next_follow_id += 1
  200. @save data, @hub, (res) =>
  201. if cb then cb(res)
  202. Page.needSite hub # Download followed user's site if necessary
  203. unfollowUser: (hub, auth_address, cb=null) ->
  204. @log "UnFollowing", hub, auth_address
  205. delete @followed_users[hub+"/"+auth_address]
  206. @getData @hub, (data) =>
  207. follow_index = i for follow, i in data.follow when follow.hub == hub and follow.auth_address == auth_address
  208. data.follow.splice(follow_index, 1)
  209. @save data, @hub, (res) =>
  210. if cb then cb(res)
  211. handleFollowClick: (e) =>
  212. @submitting_follow = true
  213. if not @isFollowed()
  214. Animation.flashIn(e.target)
  215. Page.user.followUser @hub, @auth_address, @row.user_name, (res) =>
  216. @submitting_follow = false
  217. Page.projector.scheduleRender()
  218. else
  219. Animation.flashOut(e.target)
  220. Page.user.unfollowUser @hub, @auth_address, (res) =>
  221. @submitting_follow = false
  222. Page.projector.scheduleRender()
  223. return false
  224. download: =>
  225. if not Page.merged_sites[@hub]
  226. Page.cmd "mergerSiteAdd", @hub, =>
  227. Page.updateSiteInfo()
  228. handleDownloadClick: (e) =>
  229. @download()
  230. return false
  231. handleMuteClick: (e) =>
  232. if Page.server_info.rev < 1880
  233. Page.cmd "wrapperNotification", ["info", "You need ZeroNet 0.5.2 to use this feature."]
  234. return false
  235. Page.cmd "muteAdd", [@auth_address, @row.cert_user_id, "Muted from [page](http://127.0.0.1:43110/#{Page.address}/?#{Page.history_state.url})"]
  236. return false
  237. renderList: (type="normal") =>
  238. classname = ""
  239. if type == "card" then classname = ".card"
  240. link = @getLink()
  241. followed = @isFollowed()
  242. seeding = @isSeeding()
  243. if followed then title = "Unfollow" else title = "Follow"
  244. if type != "card"
  245. enterAnimation = Animation.slideDown
  246. exitAnimation = Animation.slideUp
  247. else
  248. enterAnimation = null
  249. exitAnimation = null
  250. h("div.user"+classname, {key: @hub+"/"+@auth_address, classes: {followed: followed, notseeding: !seeding}, enterAnimation: enterAnimation, exitAnimation: exitAnimation}, [
  251. h("a.button.button-follow", {href: link, onclick: @handleFollowClick, title: title, classes: {loading: @submitting_follow}}, "+"),
  252. h("a", {href: link, onclick: Page.handleLinkClick}, @renderAvatar()),
  253. h("div.nameline", [
  254. h("a.name.link", {href: link, onclick: Page.handleLinkClick}, @row.user_name),
  255. if type == "card" then h("span.added", Time.since(@row.date_added))
  256. ])
  257. if @row.followed_by
  258. h("div.intro.followedby", [
  259. "Followed by ",
  260. h("a.name.link", {href: "?ProfileName/#{@row.followed_by}", onclick: Page.handleLinkClick}, @row.followed_by)
  261. ])
  262. else
  263. h("div.intro", @row.intro)
  264. ])
  265. window.User = User