duck1123/jiksnu

View on GitHub
src/jiksnu/actions/activity_actions.clj

Summary

Maintainability
Test Coverage
(ns jiksnu.actions.activity-actions
  (:require [ciste.config :refer [config]]
            [ciste.event :as event]
            [clojure.set :as set]
            [jiksnu.actions.user-actions :as actions.user]
            [jiksnu.model :as model]
            [jiksnu.model.activity :as model.activity]
            [jiksnu.session :as session]
            [jiksnu.templates.actions :as templates.actions]
            [jiksnu.transforms :as transforms]
            [jiksnu.transforms.activity-transforms :as transforms.activity]
            [slingshot.slingshot :refer [throw+]]
            [taoensso.timbre :as timbre]))

(defn can-delete?
  [item]
  (let [actor-id (session/current-user-id)
        author (:author item)]
    (or (session/is-admin?)
        (= actor-id author))))

(def model-ns 'jiksnu.model.activity)
(def add-link* (templates.actions/make-add-link* model.activity/collection-name))
(def index*    (templates.actions/make-indexer model-ns :sort-clause {:updated 1}))
(def delete    (templates.actions/make-delete model.activity/delete can-delete?))

;; FIXME: this is always hitting the else branch
(defn add-link
  [item link]
  (if-let [existing-link (model/get-link item (:rel link) (:type link))]
    item
    (add-link* item link)))

(defn index
  ([] (index {}))
  ([params] (index params {}))
  ([params options]
   (index* params
           (merge
            {:sort-clause {:updated -1}}
            options))))

(defn prepare-create
  [activity]
  (-> activity
      transforms/set-_id
      transforms/set-created-time
      transforms/set-updated-time
      transforms.activity/set-object-id
      transforms.activity/set-public
      transforms.activity/set-remote
      transforms.activity/set-tags
      transforms.activity/set-object-type
      transforms.activity/set-parent
      ;; transforms.activity/set-url
      transforms.activity/set-id
      transforms.activity/set-recipients
      transforms.activity/set-resources
      transforms.activity/set-mentioned
      transforms.activity/set-conversation
      transforms/set-no-links))

(defn prepare-post
  [activity]
  (-> activity
      transforms.activity/set-actor
      transforms.activity/set-verb
      transforms.activity/set-title
      transforms.activity/set-local
      transforms.activity/set-source
      transforms.activity/set-streams
      ;; transforms.activity/set-geo
      transforms.activity/set-object-updated
      transforms.activity/set-object-created
      transforms.activity/set-published-time
      transforms.activity/set-content))

(defn create
  "create an activity"
  [params]
  (let [links (:links params)
        item (dissoc params :links)
        item (prepare-create item)
        item (model.activity/create item)]
    (doseq [link links]
      (add-link item link))
    (model.activity/fetch-by-id (:_id item))))

(defn edit-page
  "Edit page for an activity"
  [id]
  ;; TODO: must be owner or admin
  (model.activity/fetch-by-id id))

(defn oembed->activity
  "Convert a oEmbed document into an activity"
  [oembed]
  (let [author (actions.user/find-or-create (get oembed "author_url"))]
    {:author (:_id author)
     :content (get oembed "html")}))

(defonce latest-entry (ref nil))

;; TODO: rename to publish
(defn post
  "Post a new activity"
  [activity]
  ;; TODO: validate user
  (if-let [prepared-post (prepare-post activity)]
    (do (-> activity :pictures model.activity/parse-pictures)
        (let [created-activity (create prepared-post)]
          (event/notify :activity-posted created-activity)
          created-activity))
    (throw+ "error preparing")))

(defn like
  [activity]
  (post {:verb "like"
         :object {:_id activity}
         :author nil}))

;; TODO: use stream update
(defn remote-create
  "Create all the activities. (multi-create)"
  [activities]
  (doseq [activity activities]
    (create activity))
  true)

(defn editable?
  [activity user]
  (and user
       (or (= (:author activity) (:_id user))
           (:admin user))))

(defn viewable?
  ([activity]
   (viewable? activity (session/current-user)))
  ([activity user]
   ;; TODO: Group membership and acl
   (or (:public activity)
       (and user
            (or (= (:author activity) (:_id user))
                (:admin user))))))

(defn show
  "Show an activity"
  [activity]
  (if (viewable? activity)
    activity
    (throw+ {:type :permission
             :message "You are not authorized to view this activity"})))

(defn edit
  "Update the current activity with this one"
  [params]
  ;; TODO: implement
  (if-let [id (:_id params)]
    (if-let [original (model.activity/fetch-by-id id)]
      (if-let [actor (session/current-user)]
        (if (editable? original actor)
          (let [original-keys (set (keys original))
                provided-keys (set/difference (set (keys params)) #{:_id})
                prohibited-keys #{:author}]
            (if (empty? (set/intersection prohibited-keys provided-keys))

              (let [changed-keys (set/intersection original-keys provided-keys)
                    removed-keys (set/difference original-keys provided-keys)
                    added-keys (set/difference provided-keys original-keys)]

                (doseq [k added-keys]
                  (model.activity/set-field! original k (get params k)))

                ;; TODO: I'm not sure these should be removed
                #_(doseq [k removed-keys]
                    (model.activity/remove-key original k))

                (doseq [k changed-keys]
                  (model.activity/set-field! original k (get params k)))

                (model.activity/fetch-by-id id))
              (throw+ "invalid keys provided")))
          (throw+ "not editable"))
        (throw+ "not authenticated"))
      (throw+ "Could not find original item"))
    (throw+ ":_id attribute is nil")))

(defn find-or-create
  [params]
  (if-let [item (or (when-let [id (:id params)]
                      (model.activity/fetch-by-remote-id id))
                    (when-let [id (:_id params)]
                      (model.activity/fetch-by-id id)))]
    item
    (create params)))

;; TODO: show action with :oembed format
(defn oembed
  [activity & [options]]
  (when activity
    (merge {:version "1.0"
            :provider_name (config :site :name)
            :provider_url "/"
            :type "link"
            :title (:title activity)
            :url (:url activity)
            :html (:content activity)}
           (let [author (model.activity/get-author activity)]
             {:author_name (:name author)
              :author_url (:uri author)}))))

(defn fetch-by-conversation
  [conversation & [options]]
  (timbre/info "Fetching activities by conversation")
  (index {:conversation (:_id conversation)} options))

(defn fetch-by-conversations
  [ids & [options]]
  (index {:conversation {:$in ids}}
         (merge
          {:sort-clause {:updated 1}}
          options)))

(defn fetch-by-feed-source
  [source & [options]]
  (index {:update-source (:_id source)} options))

(defn fetch-by-stream
  ([stream]
   (fetch-by-stream stream nil))
  ([{id :_id :as stream} options]
   (timbre/infof "Fetching activities by stream: %s" id)
   (index {:streams id} options)))

(defn fetch-by-user
  ([user]
   (fetch-by-user user nil))
  ([user options]
   (index {:author (:_id user)} options)))

(defn handle-delete-hook
  [user]
  (doseq [activity (:items (fetch-by-user user))]
    (delete activity))
  user)