<commit_before>(ns braid.chat.db.group
  (:require
    [clojure.edn :as edn]
    [datomic.api :as d]
    [braid.core.server.db :as db]
    [braid.chat.db.common :refer [create-entity-txn db->group db->user db->tag group-pull-pattern user-pull-pattern]]
    [braid.chat.db.thread :as thread]
    [braid.chat.db.user :as user]))

(defn group-exists?
  [group-name]
  (some? (d/pull (db/db) '[:group/id] [:group/name group-name])))

(defn group-by-id
  [group-id]
  (some-> (d/pull (db/db) group-pull-pattern [:group/id group-id])
          db->group))

(defn group-by-slug
  [group-slug]
  (when-let [g (d/pull (db/db) group-pull-pattern [:group/slug group-slug])]
    (db->group g)))

(defn group-users
  [group-id]
  (->> (d/q '[:find (pull ?u pull-pattern)
              :in $ ?group-id pull-pattern
              :where
              [?g :group/id ?group-id]
              [?g :group/user ?u]]
            (db/db)
            group-id
            user-pull-pattern)
       (map (comp db->user first))
       set))

(defn group-settings
  [group-id]
  (->> (d/pull (db/db) [:group/settings] [:group/id group-id])
       :group/settings
       ((fnil edn/read-string "{}"))))

(defn group-tags
  [group-id]
  (->> (d/q '[:find (pull ?t [:tag/id
                              :tag/name
                              :tag/description
                              {:tag/group [:group/id :group/name]}])
              :in $ ?group-id
              :where
              [?g :group/id ?group-id]
              [?t :tag/group ?g]]
            (db/db) group-id)
       (map (comp db->tag first))))

(defn user-groups
  [user-id]
  (->> (d/q '[:find [(pull ?g pull-pattern) ...]
             :in $ ?user-id pull-pattern
             :where
             [?u :user/id ?user-id]
             [?g :group/user ?u]]
           (db/db) user-id group-pull-pattern)
      (map db->group)
      set))

(defn user-in-group?
  [user-id group-id]
  (-> (d/q '[:find ?g
             :in $ ?user-id ?group-id
             :where
             [?u :user/id ?user-id]
             [?g :group/id ?group-id]
             [?g :group/user ?u]]
           (db/db)
           user-id group-id)
      seq
      boolean))

(defn group-users-joined-at
  [group-id]
  (->>
    (d/q '[:find ?user-id (min ?inst)
           :in $ ?group-id
           :where
           [?g :group/id ?group-id]
           [?u :user/id ?user-id]
           [?g :group/user ?u ?tx true]
           [?tx :db/txInstant ?inst]]
         (d/history (db/db))
         group-id)
    (filter (comp #(user-in-group? % group-id) first))
    (into {})))

(defn user-is-group-admin?
  [user-id group-id]
  (some?
    (d/q '[:find ?u .
           :in $ ?user-id ?group-id
           :where
           [?g :group/id ?group-id]
           [?u :user/id ?user-id]
           [?g :group/admins ?u]]
         (db/db) user-id group-id)))

;; Transactions

(defn create-group-txn
  [{:keys [name slug id]}]
  (create-entity-txn
    {:group/id id
     :group/slug slug
     :group/name name}
    db->group))

(defn group-set-txn
  "Set a key to a value for the group's settings  This will throw if
  settings are changed in between reading & setting"
  [group-id k v]
  (let [old-prefs (-> (d/pull (db/db) [:group/settings] [:group/id group-id])
                      :group/settings)
        new-prefs (-> ((fnil edn/read-string "{}") old-prefs)
                      (assoc k v)
                      pr-str)]
    [[:db.fn/cas [:group/id group-id] :group/settings old-prefs new-prefs]]))

(defn user-add-to-group-txn
  [user-id group-id]
  [[:db/add [:group/id group-id] :group/user [:user/id user-id]]])

(defn user-leave-group-txn
  [user-id group-id]
  (concat
    [[:db/retract [:group/id group-id] :group/user [:user/id user-id]]]
    ; unsubscribe from threads
    (->>
      (d/q '[:find [?t ...]
             :in $ ?user-id ?group-id
             :where
             [?u :user/id ?user-id]
             [?g :group/id ?group-id]
             [?t :thread/group ?g]
             [?u :user/subscribed-thread ?t]]
           (db/db) user-id group-id)
      (map (fn [t] [:db/retract [:user/id user-id] :user/subscribed-thread t])))
    ; remove mentions
    (->> (d/q '[:find [?t ...]
                :in $ ?user-id ?group-id
                :where
                [?u :user/id ?user-id]
                [?g :group/id ?group-id]
                [?t :thread/group ?g]
                [?t :thread/mentioned ?u]]
              (db/db) user-id group-id)
         (map (fn [t] [:db/retract t :thread/mentioned [:user/id user-id]])))
    ; remove group from custom sort order
    (if-let [order (user/user-get-preference user-id :groups-order)]
      (user/user-set-preference-txn
        user-id :groups-order (vec (remove (partial = group-id) order)))
      [])))

(defn user-make-group-admin-txn
  [user-id group-id]
  [[:db/add [:group/id group-id] :group/user [:user/id user-id]]
   [:db/add [:group/id group-id] :group/admins [:user/id user-id]]])

(defn user-subscribe-to-group-tags-txn
  "Subscribe the user to all current tags in the group"
  [user-id group-id]
  (->>
    (d/q '[:find ?tag
           :in $ ?group-id
           :where
           [?tag :tag/group ?g]
           [?g :group/id ?group-id]]
         (db/db) group-id)
    (map (fn [[tag]] [:db/add [:user/id user-id] :user/subscribed-tag tag]))))

(defn user-join-group-txn
  "Add a user to the given group, subscribe them to the group tags,
  and subscribe them to the five most recent threads in the group."
  [user-id group-id]
  (concat
    (user-add-to-group-txn user-id group-id)
    (user-subscribe-to-group-tags-txn user-id group-id)))

(defn user-open-recent-threads
  "Opens last 10 visible threads for new user.
  This can't be done in same tx as 'user-join-group' b/c that txn is the one that makes threads available for the user."
  [user-id group-id]
  (mapcat
    (fn [t] (thread/user-show-thread-txn user-id (t :id)))
    (thread/recent-threads {:user-id user-id
                            :group-id group-id
                            :num-threads 10})))<commit_msg>[chat] Fix threads not showing up for new users <commit_after>(ns braid.chat.db.group
  (:require
    [clojure.edn :as edn]
    [datomic.api :as d]
    [braid.core.server.db :as db]
    [braid.chat.db.common :refer [create-entity-txn db->group db->user db->tag group-pull-pattern user-pull-pattern]]
    [braid.chat.db.thread :as thread]
    [braid.chat.db.user :as user]))

(defn group-exists?
  [group-name]
  (some? (d/pull (db/db) '[:group/id] [:group/name group-name])))

(defn group-by-id
  [group-id]
  (some-> (d/pull (db/db) group-pull-pattern [:group/id group-id])
          db->group))

(defn group-by-slug
  [group-slug]
  (when-let [g (d/pull (db/db) group-pull-pattern [:group/slug group-slug])]
    (db->group g)))

(defn group-users
  [group-id]
  (->> (d/q '[:find (pull ?u pull-pattern)
              :in $ ?group-id pull-pattern
              :where
              [?g :group/id ?group-id]
              [?g :group/user ?u]]
            (db/db)
            group-id
            user-pull-pattern)
       (map (comp db->user first))
       set))

(defn group-settings
  [group-id]
  (->> (d/pull (db/db) [:group/settings] [:group/id group-id])
       :group/settings
       ((fnil edn/read-string "{}"))))

(defn group-tags
  [group-id]
  (->> (d/q '[:find (pull ?t [:tag/id
                              :tag/name
                              :tag/description
                              {:tag/group [:group/id :group/name]}])
              :in $ ?group-id
              :where
              [?g :group/id ?group-id]
              [?t :tag/group ?g]]
            (db/db) group-id)
       (map (comp db->tag first))))

(defn user-groups
  [user-id]
  (->> (d/q '[:find [(pull ?g pull-pattern) ...]
             :in $ ?user-id pull-pattern
             :where
             [?u :user/id ?user-id]
             [?g :group/user ?u]]
           (db/db) user-id group-pull-pattern)
      (map db->group)
      set))

(defn user-in-group?
  [user-id group-id]
  (-> (d/q '[:find ?g
             :in $ ?user-id ?group-id
             :where
             [?u :user/id ?user-id]
             [?g :group/id ?group-id]
             [?g :group/user ?u]]
           (db/db)
           user-id group-id)
      seq
      boolean))

(defn group-users-joined-at
  [group-id]
  (->>
    (d/q '[:find ?user-id (min ?inst)
           :in $ ?group-id
           :where
           [?g :group/id ?group-id]
           [?u :user/id ?user-id]
           [?g :group/user ?u ?tx true]
           [?tx :db/txInstant ?inst]]
         (d/history (db/db))
         group-id)
    (filter (comp #(user-in-group? % group-id) first))
    (into {})))

(defn user-is-group-admin?
  [user-id group-id]
  (some?
    (d/q '[:find ?u .
           :in $ ?user-id ?group-id
           :where
           [?g :group/id ?group-id]
           [?u :user/id ?user-id]
           [?g :group/admins ?u]]
         (db/db) user-id group-id)))

;; Transactions

(defn create-group-txn
  [{:keys [name slug id]}]
  (create-entity-txn
    {:group/id id
     :group/slug slug
     :group/name name}
    db->group))

(defn group-set-txn
  "Set a key to a value for the group's settings  This will throw if
  settings are changed in between reading & setting"
  [group-id k v]
  (let [old-prefs (-> (d/pull (db/db) [:group/settings] [:group/id group-id])
                      :group/settings)
        new-prefs (-> ((fnil edn/read-string "{}") old-prefs)
                      (assoc k v)
                      pr-str)]
    [[:db.fn/cas [:group/id group-id] :group/settings old-prefs new-prefs]]))

(defn user-add-to-group-txn
  [user-id group-id]
  [[:db/add [:group/id group-id] :group/user [:user/id user-id]]])

(defn user-leave-group-txn
  [user-id group-id]
  (concat
    [[:db/retract [:group/id group-id] :group/user [:user/id user-id]]]
    ; unsubscribe from threads
    (->>
      (d/q '[:find [?t ...]
             :in $ ?user-id ?group-id
             :where
             [?u :user/id ?user-id]
             [?g :group/id ?group-id]
             [?t :thread/group ?g]
             [?u :user/subscribed-thread ?t]]
           (db/db) user-id group-id)
      (map (fn [t] [:db/retract [:user/id user-id] :user/subscribed-thread t])))
    ; remove mentions
    (->> (d/q '[:find [?t ...]
                :in $ ?user-id ?group-id
                :where
                [?u :user/id ?user-id]
                [?g :group/id ?group-id]
                [?t :thread/group ?g]
                [?t :thread/mentioned ?u]]
              (db/db) user-id group-id)
         (map (fn [t] [:db/retract t :thread/mentioned [:user/id user-id]])))
    ; remove group from custom sort order
    (if-let [order (user/user-get-preference user-id :groups-order)]
      (user/user-set-preference-txn
        user-id :groups-order (vec (remove (partial = group-id) order)))
      [])))

(defn user-make-group-admin-txn
  [user-id group-id]
  [[:db/add [:group/id group-id] :group/user [:user/id user-id]]
   [:db/add [:group/id group-id] :group/admins [:user/id user-id]]])

(defn user-subscribe-to-group-tags-txn
  "Subscribe the user to all current tags in the group"
  [user-id group-id]
  (->>
    (d/q '[:find ?tag
           :in $ ?group-id
           :where
           [?tag :tag/group ?g]
           [?g :group/id ?group-id]]
         (db/db) group-id)
    (map (fn [[tag]] [:db/add [:user/id user-id] :user/subscribed-tag tag]))))

(defn user-subscribe-to-all-tagged-threads
  "Subscribe the user to all tagged conversation threads (ie. public).
   Without this, threads that were started *before* a new user registered
   would not show up for that user even if new messages were posted on those threads."
  [user-id group-id]
  (->> (d/q '[:find [?thread ...]
              :in $ ?group-id
              :where
              [?group :group/id ?group-id]
              [?tag :tag/group ?group]
              [?thread :thread/tag ?tag]
              [?thread :thread/group ?group]]
            (db/db) group-id)
       (map (fn [thread]
              [:db/add [:user/id user-id] :user/subscribed-thread thread]))))

(defn user-join-group-txn
  "Add a user to the given group, subscribe them to the group tags,
  and subscribe them to the five most recent threads in the group."
  [user-id group-id]
  (concat
    (user-add-to-group-txn user-id group-id)
    (user-subscribe-to-group-tags-txn user-id group-id)
    (user-subscribe-to-all-tagged-threads user-id group-id)))

(defn user-open-recent-threads
  "Opens last 10 visible threads for new user.
  This can't be done in same tx as 'user-join-group' b/c that txn is the one that makes threads available for the user."
  [user-id group-id]
  (mapcat
    (fn [t] (thread/user-show-thread-txn user-id (t :id)))
    (thread/recent-threads {:user-id user-id
                            :group-id group-id
                            :num-threads 10})))
{'fraction_non_alphanumeric': 0.17361057432171292, 'fraction_numerical': 0.0006183813867202598, 'mean_word_length': 1.7871607065919861, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 63, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6245883', 'n_tokens_mistral': 4781, 'n_tokens_neox': 4722, 'n_words': 1146}