Repository: CSCfi/rems
Pull request #1062 (2019-04-04 11:35:38)

Refactor: decouple more things from the old application model
#852 

Changing some more places to use v2 application. After these changes, entitlements work only for dynamic applications.


Patch:
diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql
index e33e81174..43d142cea 100644
--- a/resources/sql/queries.sql
+++ b/resources/sql/queries.sql
@@ -41,6 +41,18 @@ WHERE 1=1
 /*~ ) ~*/
 ;
 
+-- :name get-catalogue-item :? :1
+SELECT ci.id, ci.title, res.resid, ci.wfid, ci.formid, ci.start, ci.endt as "end", ci.enabled, ci.archived
+, res.id AS "resource-id"
+, wf.title AS "workflow-name"
+, res.resid AS "resource-name"
+, form.title AS "form-name"
+FROM catalogue_item ci
+LEFT OUTER JOIN resource res ON (ci.resid = res.id)
+LEFT OUTER JOIN workflow wf ON (ci.wfid = wf.id)
+LEFT OUTER JOIN application_form form ON (ci.formid = form.id)
+WHERE ci.id = :id;
+
 -- :name set-catalogue-item-state! :insert
 -- TODO set modifieruserid?
 UPDATE catalogue_item
@@ -50,9 +62,6 @@ SET
 /*~ ) ~*/
 /*~ (when (boolean? (:archived params)) */
   archived = :archived,
-/*~ ) ~*/
-/*~ (when (contains? params :endt) */
-  endt = :endt,
 /*~ ) ~*/
   id = id
 WHERE id = :id;
@@ -336,13 +345,8 @@ WHERE 1=1
 ;
 
 -- :name add-entitlement! :!
--- TODO remove resId from this table to make it normalized?
 INSERT INTO entitlement (catAppId, userId, resId)
-SELECT :application, :user, cat.resid
-FROM catalogue_item_application_items ciai
-LEFT OUTER JOIN catalogue_item_application app ON ciai.catAppId = app.id
-LEFT OUTER JOIN catalogue_item cat ON ciai.catItemId = cat.id
-WHERE ciai.catAppId = :application;
+VALUES (:application, :user, :resource);
 
 -- :name end-entitlement! :!
 UPDATE entitlement
diff --git a/resources/translations/en.edn b/resources/translations/en.edn
index 9d29ed28e..d29c0102e 100644
--- a/resources/translations/en.edn
+++ b/resources/translations/en.edn
@@ -101,10 +101,10 @@
                    :enable "Enable"
                    :end "End"
                    :errors {:duplicate-resid "Duplicate resource for organization."
-                            :form-in-use "Form is in use by:"
-                            :license-in-use "License is in use by:"
-                            :resource-in-use "Resource is in use by:"
-                            :workflow-in-use "Workflow is in use by:"}
+                            :form-in-use "Form is in use."
+                            :license-in-use "Lisenssi käytössä."
+                            :resource-in-use "Resource is in use."
+                            :workflow-in-use "Workflow is in use."}
                    :field "Field \"%1\""
                    :fields "Fields"
                    :form "Form"
diff --git a/resources/translations/fi.edn b/resources/translations/fi.edn
index 87357c95b..207c68097 100644
--- a/resources/translations/fi.edn
+++ b/resources/translations/fi.edn
@@ -101,10 +101,10 @@
                    :enable "Ota käyttöön"
                    :end "Loppu"
                    :errors {:duplicate-resid "Resurssi on jo olemassa organisaatiossa."
-                            :form-in-use "Lomake on käytössä täällä:"
-                            :license-in-use "Lisenssi käytössä täällä:"
-                            :resource-in-use "Resurssi on käytössä täällä:"
-                            :workflow-in-use "Työvuo on käytössä täällä:"}
+                            :form-in-use "Lomake on käytössä."
+                            :license-in-use "Lisenssi käytössä."
+                            :resource-in-use "Resurssi on käytössä."
+                            :workflow-in-use "Työvuo on käytössä."}
                    :field "Kenttä \"%1\""
                    :fields "Kentät"
                    :form "Lomake"
diff --git a/src/clj/rems/api/applications.clj b/src/clj/rems/api/applications.clj
index 3c3032370..247ee338e 100644
--- a/src/clj/rems/api/applications.clj
+++ b/src/clj/rems/api/applications.clj
@@ -8,6 +8,7 @@
             [rems.application.commands :as commands]
             [rems.auth.util :refer [throw-forbidden]]
             [rems.db.applications :as applications]
+            [rems.db.attachments :as attachments]
             [rems.db.core :as db]
             [rems.db.users :as users]
             [rems.pdf :as pdf]
@@ -110,20 +111,6 @@
 
 (def get-deciders get-users)
 
-(defn- check-attachment-content-type
-  "Checks that content-type matches the allowed ones listed on the UI side:
-   .pdf, .doc, .docx, .ppt, .pptx, .txt, image/*"
-  [content-type]
-  (when-not (or (#{"application/pdf"
-                   "application/msword"
-                   "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
-                   "application/vnd.ms-powerpoint"
-                   "application/vnd.openxmlformats-officedocument.presentationml.presentation"
-                   "text/plain"}
-                 content-type)
-                (.startsWith content-type "image/"))
-    (throw (rems.InvalidRequestException. (str "Unsupported content-type: " content-type)))))
-
 (defn- fix-command-from-api
   [cmd]
   ;; schema could do these coercions for us...
@@ -156,17 +143,10 @@
       :roles #{:logged-in}
       :query-params [application-id :- (describe s/Int "application id")
                      field-id :- (describe s/Int "application form field id the attachment is related to")]
-      (let [user-id (getx-user-id)
-            application (applications/get-dynamic-application-state-for-user user-id application-id)]
-        (if-let [attachment (db/get-attachment {:item field-id
-                                                :form (:form/id application)
-                                                :application application-id})]
-          (do (check-attachment-content-type (:type attachment))
-              (-> (:data attachment)
-                  (ByteArrayInputStream.)
-                  (ok)
-                  (content-type (:type attachment))))
-          (not-found! "not found"))))
+      (if-let [attachment (attachments/get-attachment (getx-user-id) application-id field-id)]
+        (-> (ok (:data attachment))
+            (content-type (:content-type attachment)))
+        (not-found! "not found")))
 
     (POST "/accept-invitation" []
       :summary "Accept an invitation by token"
@@ -197,8 +177,7 @@
                      field-id :- (describe s/Int "application form field id the attachment is related to")]
       :middleware [multipart/wrap-multipart-params]
       :return SuccessResponse
-      (check-attachment-content-type (:content-type file))
-      (applications/save-attachment! file (getx-user-id) application-id field-id)
+      (attachments/save-attachment! file (getx-user-id) application-id field-id)
       (ok {:success true}))
 
     (POST "/remove_attachment" []
@@ -207,7 +186,7 @@
       :query-params [application-id :- (describe s/Int "application id")
                      field-id :- (describe s/Int "application form field id the attachment is related to")]
       :return SuccessResponse
-      (applications/remove-attachment! (getx-user-id) application-id field-id)
+      (attachments/remove-attachment! (getx-user-id) application-id field-id)
       (ok {:success true}))
 
     (POST "/command" []
diff --git a/src/clj/rems/api/applications_v2.clj b/src/clj/rems/api/applications_v2.clj
index 32ebc40f1..2c4cf3051 100644
--- a/src/clj/rems/api/applications_v2.clj
+++ b/src/clj/rems/api/applications_v2.clj
@@ -2,7 +2,6 @@
   (:require [rems.application.model :as model]
             [rems.auth.util :refer [throw-forbidden]]
             [rems.db.applications :as applications]
-            [rems.db.catalogue :as catalogue]
             [rems.db.core :as db]
             [rems.db.form :as form]
             [rems.db.licenses :as licenses]
@@ -17,7 +16,7 @@
 (defn- get-catalogue-item [catalogue-item-id]
   (assert (int? catalogue-item-id)
           (pr-str catalogue-item-id))
-  (catalogue/get-localized-catalogue-item catalogue-item-id))
+  (applications/get-catalogue-item catalogue-item-id))
 
 (defn- get-license [license-id]
   (licenses/get-license license-id))
@@ -30,7 +29,19 @@
                            :get-license get-license
                            :get-user get-user})
 
-(defn api-get-application-v2 [user-id application-id]
+(defn get-unrestricted-application
+  "Returns the full application state without any user permission
+   checks and filtering of sensitive information. Don't expose via APIs."
+  [application-id]
+  (let [events (applications/get-dynamic-application-events application-id)]
+    (if (empty? events)
+      nil
+      (model/build-application-view events injections))))
+
+(defn api-get-application-v2
+  "Returns the part of application state which the specified user
+   is allowed to see. Suitable for returning from public APIs as-is."
+  [user-id application-id]
   (let [events (applications/get-dynamic-application-events application-id)]
     (if (empty? events)
       nil ;; will result in a 404
@@ -52,22 +63,22 @@
   (let [form (:application/form application)
         workflow (:application/workflow application)
         catalogue-items (map (fn [resource]
-                               {:id (:catalogue-item/id resource)
-                                :resource-id (:resource/id resource)
-                                :resid (:resource/ext-id resource)
-                                :wfid (:workflow/id workflow)
-                                :formid (:form/id form)
-                                :start (:catalogue-item/start resource)
-                                :end (:catalogue-item/end resource)
-                                :archived (:catalogue-item/archived resource)
-                                :enabled (:catalogue-item/enabled resource)
-                                :active (:catalogue-item/active resource)
-                                :title (:default (:catalogue-item/title resource))
-                                :localizations (into {} (for [lang (-> (set (keys (:catalogue-item/title resource)))
-                                                                       (disj :default))]
-                                                          [lang {:title (get-in resource [:catalogue-item/title lang])
-                                                                 :langcode lang
-                                                                 :id (:catalogue-item/id resource)}]))})
+                               (applications/translate-catalogue-item
+                                {:id (:catalogue-item/id resource)
+                                 :resource-id (:resource/id resource)
+                                 :resid (:resource/ext-id resource)
+                                 :wfid (:workflow/id workflow)
+                                 :formid (:form/id form)
+                                 :start (:catalogue-item/start resource)
+                                 :end (:catalogue-item/end resource)
+                                 :archived (:catalogue-item/archived resource)
+                                 :enabled (:catalogue-item/enabled resource)
+                                 :title (:default (:catalogue-item/title resource))
+                                 :localizations (into {} (for [lang (-> (set (keys (:catalogue-item/title resource)))
+                                                                        (disj :default))]
+                                                           [lang {:title (get-in resource [:catalogue-item/title lang])
+                                                                  :langcode lang
+                                                                  :id (:catalogue-item/id resource)}]))}))
                              (:application/resources application))]
     {:id (:form/id form)
      :title (:form/title form)
@@ -108,7 +119,6 @@
                        :end (:license/end license)
                        :enabled (:license/enabled license)
                        :archived (:license/archived license)
-                       :active (:license/active license)
                        :approved (:license/accepted license)
                        :title (:default (:license/title license))
                        :textcontent (:default (or (:license/link license)
diff --git a/src/clj/rems/api/forms.clj b/src/clj/rems/api/forms.clj
index 81e1a0538..e2b825d74 100644
--- a/src/clj/rems/api/forms.clj
+++ b/src/clj/rems/api/forms.clj
@@ -69,11 +69,9 @@
     (GET "/" []
       :summary "Get forms"
       :roles #{:owner}
-      :query-params [{active :- (describe s/Bool "filter active or inactive forms") nil}
-                     {archived :- (describe s/Bool "whether to include archived resources") false}]
+      :query-params [{active :- (describe s/Bool "filter active or inactive forms") nil}]
       :return Forms
-      (ok (get-forms (merge (when active {:active active})
-                            (when-not archived {:archived false})))))
+      (ok (get-forms (when active {:active active}))))
 
     (GET "/:form-id" []
       :summary "Get form by id"
diff --git a/src/clj/rems/api/licenses.clj b/src/clj/rems/api/licenses.clj
index 953ee7d3b..0b35d145f 100644
--- a/src/clj/rems/api/licenses.clj
+++ b/src/clj/rems/api/licenses.clj
@@ -47,11 +47,9 @@
     (GET "/" []
       :summary "Get licenses"
       :roles #{:owner}
-      :query-params [{active :- (describe s/Bool "filter active or inactive licenses") nil}
-                     {archived :- (describe s/Bool "whether to include archived resources") false}]
+      :query-params [{active :- (describe s/Bool "filter active or inactive licenses") nil}]
       :return Licenses
-      (ok (licenses/get-all-licenses (merge (when active {:active active})
-                                            (when-not archived {:archived false})))))
+      (ok (licenses/get-all-licenses (when active {:active active}))))
 
     (GET "/:license-id" []
       :summary "Get license"
diff --git a/src/clj/rems/api/schema.clj b/src/clj/rems/api/schema.clj
index 6a6be8417..ae4202f4f 100644
--- a/src/clj/rems/api/schema.clj
+++ b/src/clj/rems/api/schema.clj
@@ -19,7 +19,6 @@
    :end (s/maybe DateTime)
    :enabled s/Bool
    :archived s/Bool
-   :active s/Bool
    :localizations (s/maybe {s/Any s/Any})})
 
 (s/defschema License
@@ -29,7 +28,6 @@
    :end (s/maybe DateTime)
    :enabled s/Bool
    :archived s/Bool
-   :active s/Bool
    :title s/Str
    :textcontent s/Str
    (s/optional-key :attachment-id) (s/maybe s/Num)
@@ -131,7 +129,6 @@
    :catalogue-item/start DateTime
    :catalogue-item/end (s/maybe DateTime)
    :catalogue-item/enabled s/Bool
-   :catalogue-item/active s/Bool
    :catalogue-item/archived s/Bool})
 
 (s/defschema V2License
@@ -146,7 +143,6 @@
    :license/start DateTime
    :license/end (s/maybe DateTime)
    :license/enabled s/Bool
-   :license/active s/Bool
    :license/archived s/Bool})
 
 (s/defschema V2Field
diff --git a/src/clj/rems/api/workflows.clj b/src/clj/rems/api/workflows.clj
index 7b9f6d715..b1bf96889 100644
--- a/src/clj/rems/api/workflows.clj
+++ b/src/clj/rems/api/workflows.clj
@@ -113,11 +113,9 @@
     (GET "/" []
       :summary "Get workflows"
       :roles #{:owner}
-      :query-params [{active :- (describe s/Bool "filter active or inactive workflows") nil}
-                     {archived :- (describe s/Bool "whether to include archived resources") false}]
+      :query-params [{active :- (describe s/Bool "filter active or inactive workflows") nil}]
       :return Workflows
-      (ok (get-workflows (merge (when active {:active active})
-                                (when-not archived {:archived false})))))
+      (ok (get-workflows (when active {:active active}))))
 
     (POST "/create" []
       :summary "Create workflow"
diff --git a/src/clj/rems/application/model.clj b/src/clj/rems/application/model.clj
index 1fb111c99..0ae3e26d2 100644
--- a/src/clj/rems/application/model.clj
+++ b/src/clj/rems/application/model.clj
@@ -402,7 +402,6 @@
                :catalogue-item/start (:start item)
                :catalogue-item/end (:end item)
                :catalogue-item/enabled (:enabled item)
-               :catalogue-item/active (:active item)
                :catalogue-item/archived (:archived item)}))
        (sort-by :catalogue-item/id)))
 
@@ -420,7 +419,6 @@
                                             :license/start (:start license)
                                             :license/end (:end license)
                                             :license/enabled (:enabled license)
-                                            :license/active (:active license)
                                             :license/archived (:archived license)}
                                            (case license-type
                                              :text {:license/text (assoc (localization-for :textcontent license)
diff --git a/src/clj/rems/db/applications.clj b/src/clj/rems/db/applications.clj
index c1752a80c..7c6095388 100644
--- a/src/clj/rems/db/applications.clj
+++ b/src/clj/rems/db/applications.clj
@@ -138,6 +138,38 @@
   (and (is-applicant? user-id application)
        (= (:state application) "applied")))
 
+(defn translate-catalogue-item [item]
+  (merge item
+         (get-in item [:localizations context/*lang*])))
+
+(defn get-catalogue-items
+  "Function that returns localized catalogue-items for the given application items, `ids`. Prefetched localized catalogue items, `localized-items`,
+  can be given as a parameter to avoid excessive database calls."
+  ([ids]
+   (mapv translate-catalogue-item
+         (catalogue/get-localized-catalogue-items {:ids ids})))
+  ([ids localized-items]
+   (mapv translate-catalogue-item
+         (filter #(some #{(:id %)} ids)
+                 localized-items))))
+
+(defn get-catalogue-item [id]
+  (translate-catalogue-item (catalogue/get-localized-catalogue-item id)))
+
+(defn get-catalogue-items-by-application-id
+  "Given an `app-id`, the function queries for all the items related to that application and calls `get-catalogue-items` to return all the catalogue items
+  for the application with localizations."
+  [app-id]
+  (get-catalogue-items (mapv :item (db/get-application-items {:application app-id}))))
+
+(defn- get-catalogue-items-by-application-items
+  "Given `application-items` and `localized-items`, catalogue items with localizations, the function `get-catalogue-items` to map all the application items
+  to the catalogue items with localizations."
+  [application-items localized-items]
+  (when (seq application-items)
+    (get-catalogue-items (mapv :item application-items)
+                         localized-items)))
+
 (defn- get-field-value [field form-id application-id]
   (let [query-params {:item (:id field)
                       :form form-id
@@ -300,7 +332,7 @@
          form-id (:formid form)
          _ (assert form-id)
          catalogue-item-ids (mapv :item (db/get-application-items {:application application-id}))
-         catalogue-items (catalogue/get-localized-catalogue-items {:ids catalogue-item-ids})
+         catalogue-items (get-catalogue-items catalogue-item-ids)
          items (->> (db/get-form-items {:id form-id})
                     (mapv #(process-field application-id form-id %))
                     (assoc-field-previous-values application))
@@ -336,32 +368,6 @@
       :accepted-licenses (get-in application [:form-contents :accepted-licenses])
       :phases (get-application-phases (:state application))})))
 
-(defn save-attachment!
-  [{:keys [tempfile filename content-type]} user-id application-id item-id]
-  (let [application (get-dynamic-application-state-for-user user-id application-id)
-        byte-array (with-open [input (FileInputStream. tempfile)
-                               buffer (ByteArrayOutputStream.)]
-                     (clojure.java.io/copy input buffer)
-                     (.toByteArray buffer))]
-    (when-not (form-fields-editable? application)
-      (throw-forbidden))
-    (db/save-attachment! {:application application-id
-                          :form (:form/id application)
-                          :item item-id
-                          :user user-id
-                          :filename filename
-                          :type content-type
-                          :data byte-array})))
-
-(defn remove-attachment!
-  [user-id application-id item-id]
-  (let [application (get-dynamic-application-state-for-user user-id application-id)]
-    (when-not (form-fields-editable? application)
-      (throw-forbidden))
-    (db/remove-attachment! {:application application-id
-                            :form (:form/id application)
-                            :item item-id})))
-
 (defn create-new-draft [user-id wfid]
   (assert user-id)
   (assert wfid)
@@ -713,7 +719,7 @@
 
 (defn application-created-event [{:keys [application-id catalogue-item-ids time actor allocate-external-id?]}]
   (assert (seq catalogue-item-ids) "catalogue item not specified")
-  (let [items (catalogue/get-localized-catalogue-items {:ids catalogue-item-ids})]
+  (let [items (get-catalogue-items catalogue-item-ids)]
     (assert (= (count items) (count catalogue-item-ids)) "catalogue item not found")
     (assert (= 1 (count (distinct (mapv :wfid items)))) "catalogue items did not have the same workflow")
     (assert (= 1 (count (distinct (mapv :formid items)))) "catalogue items did not have the same form")
diff --git a/src/clj/rems/db/attachments.clj b/src/clj/rems/db/attachments.clj
new file mode 100644
index 000000000..c9c76101e
--- /dev/null
+++ b/src/clj/rems/db/attachments.clj
@@ -0,0 +1,58 @@
+(ns rems.db.attachments
+  (:require [rems.api.applications-v2 :as applications-v2]
+            [rems.application-util :refer [form-fields-editable?]]
+            [rems.auth.util :refer [throw-forbidden]]
+            [rems.db.core :as db])
+  (:import [java.io ByteArrayOutputStream FileInputStream File ByteArrayInputStream]
+           [rems InvalidRequestException]))
+
+(defn- check-attachment-content-type
+  "Checks that content-type matches the allowed ones listed on the UI side:
+   .pdf, .doc, .docx, .ppt, .pptx, .txt, image/*"
+  [content-type]
+  (when-not (or (#{"application/pdf"
+                   "application/msword"
+                   "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+                   "application/vnd.ms-powerpoint"
+                   "application/vnd.openxmlformats-officedocument.presentationml.presentation"
+                   "text/plain"}
+                 content-type)
+                (.startsWith content-type "image/"))
+    (throw (InvalidRequestException. (str "Unsupported content-type: " content-type)))))
+
+(defn save-attachment!
+  [{:keys [tempfile filename content-type]} user-id application-id item-id]
+  (check-attachment-content-type content-type)
+  (let [application (applications-v2/api-get-application-v2 user-id application-id)
+        byte-array (with-open [input (FileInputStream. ^File tempfile)
+                               buffer (ByteArrayOutputStream.)]
+                     (clojure.java.io/copy input buffer)
+                     (.toByteArray buffer))]
+    (when-not (form-fields-editable? application)
+      (throw-forbidden))
+    (db/save-attachment! {:application application-id
+                          :form (get-in application [:application/form :form/id])
+                          :item item-id
+                          :user user-id
+                          :filename filename
+                          :type content-type
+                          :data byte-array})))
+
+(defn remove-attachment! [user-id application-id item-id]
+  (let [application (applications-v2/api-get-application-v2 user-id application-id)]
+    (when-not (form-fields-editable? application)
+      (throw-forbidden))
+    (db/remove-attachment! {:application application-id
+                            :form (get-in application [:application/form :form/id])
+                            :item item-id})))
+
+(defn get-attachment [user-id application-id field-id]
+  (let [application (applications-v2/api-get-application-v2 user-id application-id)
+        form-id (get-in application [:application/form :form/id])]
+    (when-let [attachment (db/get-attachment {:item field-id
+                                              :form form-id
+                                              :application application-id})]
+      (check-attachment-content-type (:type attachment))
+      {:data (-> (:data attachment)
+                 (ByteArrayInputStream.))
+       :content-type (:type attachment)})))
diff --git a/src/clj/rems/db/catalogue.clj b/src/clj/rems/db/catalogue.clj
index d0c995a06..c2ea83af8 100644
--- a/src/clj/rems/db/catalogue.clj
+++ b/src/clj/rems/db/catalogue.clj
@@ -28,12 +28,11 @@
   ([]
    (get-localized-catalogue-items {}))
   ([query-params]
-   (->> (db/get-catalogue-items query-params)
-        (map localize-catalogue-item)
-        (map db/assoc-active))))
+   (map localize-catalogue-item (db/get-catalogue-items query-params))))
 
 (defn get-localized-catalogue-item [id]
-  (first (get-localized-catalogue-items {:ids [id] :archived true})))
+  (when-let [item (db/get-catalogue-item {:id id})]
+    (localize-catalogue-item item)))
 
 (defn create-catalogue-item! [command]
   (let [id (:id (db/create-catalogue-item! (select-keys command [:title :form :resid :wfid :enabled :archived])))]
diff --git a/src/clj/rems/db/entitlements.clj b/src/clj/rems/db/entitlements.clj
index 0d019a780..a2d4dec74 100644
--- a/src/clj/rems/db/entitlements.clj
+++ b/src/clj/rems/db/entitlements.clj
@@ -67,18 +67,25 @@
   and call the entitlement REST callback (if defined)."
   [application]
   ;; TODO this is not idempotent
-  (when (contains? #{:application.state/approved "approved"} (:state application))
-    (log/info "granting entitlements on application" (:id application) "to" (:applicantuserid application))
-    (db/add-entitlement! {:application (:id application)
-                          :user (:applicantuserid application)})
-    (post-entitlements :add (db/get-entitlements {:application (:id application)}))))
+  (when (= :application.state/approved (:application/state application))
+    (let [app-id (:application/id application)
+          user-id (:application/applicant application)]
+      (log/info "granting entitlements on application" app-id "to" user-id)
+      (doseq [resource-id (->> (:application/resources application)
+                               (map :resource/id))]
+        (db/add-entitlement! {:application app-id
+                              :user user-id
+                              :resource resource-id}))
+      (post-entitlements :add (db/get-entitlements {:application app-id})))))
 
 (defn- end-entitlements-for
   [application]
-  (when (contains? #{:application.state/closed "closed"} (:state application))
-    (log/info "ending entitlements on application" (:id application) "to" (:applicantuserid application))
-    (db/end-entitlement! {:application (:id application)})
-    (post-entitlements :remove (db/get-entitlements {:application (:id application)}))))
+  (when (= :application.state/closed (:application/state application))
+    (let [app-id (:application/id application)
+          user-id (:application/applicant application)]
+      (log/info "ending entitlements on application" app-id "to" user-id)
+      (db/end-entitlement! {:application app-id})
+      (post-entitlements :remove (db/get-entitlements {:application app-id})))))
 
 (defn update-entitlements-for
   [application]
diff --git a/src/clj/rems/db/form.clj b/src/clj/rems/db/form.clj
index bfecba2cc..eb307a015 100644
--- a/src/clj/rems/db/form.clj
+++ b/src/clj/rems/db/form.clj
@@ -1,6 +1,5 @@
 (ns rems.db.form
   (:require [rems.InvalidRequestException]
-            [rems.db.catalogue :as catalogue]
             [rems.db.core :as db]
             [rems.json :as json]))
 
@@ -60,11 +59,10 @@
      :id form-id}))
 
 (defn update-form! [command]
-  (let [catalogue-items (->> (catalogue/get-localized-catalogue-items {:form (:id command) :archived false})
-                             (map #(select-keys % [:id :title :localizations])))]
+  (let [catalogue-items (db/get-catalogue-items {:form (:id command) :archived false})]
     (if (and (:archived command) (seq catalogue-items))
       {:success false
-       :errors [{:type :t.administration.errors/form-in-use :catalogue-items catalogue-items}]}
+       :errors [{:type :t.administration.errors/form-in-use :catalogue-items (mapv :id catalogue-items)}]}
       (do
         (db/set-form-state! command)
         (db/set-form-template-state! command)
diff --git a/src/clj/rems/db/licenses.clj b/src/clj/rems/db/licenses.clj
index ccbb84541..429c66764 100644
--- a/src/clj/rems/db/licenses.clj
+++ b/src/clj/rems/db/licenses.clj
@@ -1,9 +1,7 @@
 (ns rems.db.licenses
   "querying localized licenses"
   (:require [rems.common-util :refer [distinct-by]]
-            [rems.db.core :as db]
-            [rems.db.resource :as resource]
-            [rems.db.workflow :as workflow])
+            [rems.db.core :as db])
   (:import (java.io FileInputStream ByteArrayOutputStream)))
 
 (defn- format-license [license]
@@ -13,7 +11,6 @@
    :end (:endt license)
    :enabled (:enabled license)
    :archived (:archived license)
-   :active (:active license)
    ;; TODO why do licenses have a non-localized title & content while items don't?
    :title (:title license)
    :textcontent (:textcontent license)
@@ -42,7 +39,6 @@
   [id]
   (->> (db/get-resource-licenses {:id id})
        (format-licenses)
-       (mapv db/assoc-active)
        (localize-licenses)))
 
 (defn get-license
@@ -81,8 +77,7 @@
   (->> (db/get-licenses params)
        (format-licenses)
        (localize-licenses)
-       (map (partial db/assoc-active now))
-       (filter :active)
+       (filter (fn [license] (db/now-active? now (:start license) (:end license))))
        (distinct-by :id)))
 
 (defn create-license! [{:keys [title licensetype textcontent localizations attachment-id]} user-id]
@@ -122,14 +117,14 @@
   ;; these could be db joins
   (let [resources (->> (db/get-resources-for-license {:id id})
                        (map :resid)
-                       (map resource/get-resource)
+                       (map #(db/get-resource {:id %}))
                        (remove :archived)
-                       (map #(select-keys % [:id :resid])))
+                       (map :id))
         workflows (->> (db/get-workflows-for-license {:id id})
                        (map :wfid)
-                       (map workflow/get-workflow)
+                       (map #(db/get-workflow {:wfid %}))
                        (remove :archived)
-                       (map #(select-keys % [:id :title])))]
+                       (map :id))]
     (when (or (seq resources) (seq workflows))
       {:resources resources
        :workflows workflows})))
diff --git a/src/clj/rems/db/resource.clj b/src/clj/rems/db/resource.clj
index 989702996..724f071a2 100644
--- a/src/clj/rems/db/resource.clj
+++ b/src/clj/rems/db/resource.clj
@@ -1,6 +1,5 @@
 (ns rems.db.resource
-  (:require [rems.db.catalogue :as catalogue]
-            [rems.db.core :as db])
+  (:require [rems.db.core :as db])
   (:import (org.postgresql.util PSQLException)))
 
 (defn get-resource [id]
@@ -38,11 +37,10 @@
         (throw e)))))
 
 (defn update-resource! [command]
-  (let [catalogue-items (->> (catalogue/get-localized-catalogue-items {:resource-id (:id command) :archived false})
-                             (map #(select-keys % [:id :title :localizations])))]
+  (let [catalogue-items (db/get-catalogue-items {:resource-id (:id command) :archived false})]
     (if (and (:archived command) (seq catalogue-items))
       {:success false
-       :errors [{:type :t.administration.errors/resource-in-use :catalogue-items catalogue-items}]}
+       :errors [{:type :t.administration.errors/resource-in-use :catalogue-items (mapv :id catalogue-items)}]}
       (do
         (db/set-resource-state! (select-keys command [:id :enabled :archived]))
         {:success true}))))
diff --git a/src/clj/rems/db/workflow.clj b/src/clj/rems/db/workflow.clj
index 51ca75ce3..8bc127a29 100644
--- a/src/clj/rems/db/workflow.clj
+++ b/src/clj/rems/db/workflow.clj
@@ -1,6 +1,5 @@
 (ns rems.db.workflow
-  (:require [rems.db.catalogue :as catalogue]
-            [rems.db.core :as db]
+  (:require [rems.db.core :as db]
             [rems.json :as json]))
 
 (defn- parse-workflow-body [json]
@@ -52,12 +51,10 @@
      {:success (not (nil? (:id result)))})))
 
 (defn update-workflow! [command]
-  (let [catalogue-items
-        (->> (catalogue/get-localized-catalogue-items {:workflow (:id command) :archived false})
-             (map #(select-keys % [:id :title :localizations])))]
+  (let [catalogue-items (db/get-catalogue-items {:workflow (:id command) :archived false})]
     (if (and (:archived command) (seq catalogue-items))
       {:success false
-       :errors [{:type :t.administration.errors/workflow-in-use :catalogue-items catalogue-items}]}
+       :errors [{:type :t.administration.errors/workflow-in-use :catalogue-items (mapv :id catalogue-items)}]}
       (do
         (db/set-workflow-state! command)
         {:success true}))))
diff --git a/src/clj/rems/poller/email.clj b/src/clj/rems/poller/email.clj
index 7d04df54b..c9e1e1015 100644
--- a/src/clj/rems/poller/email.clj
+++ b/src/clj/rems/poller/email.clj
@@ -4,13 +4,14 @@
             [clojure.tools.logging :as log]
             [mount.core :as mount]
             [postal.core :as postal]
+            [rems.api.applications-v2 :as applications-v2]
             [rems.config :refer [env]]
             [rems.db.applications :as applications]
             [rems.db.users :as users]
-            [rems.text :refer [text text-format with-language]]
             [rems.poller.common :as common]
-            [rems.util :as util]
-            [rems.workflow.dynamic :as dynamic]))
+            [rems.text :refer [text text-format with-language]]
+            [rems.util :as util])
+  (:import [java.util.concurrent ScheduledThreadPoolExecutor TimeUnit]))
 
 ;;; Mapping events to emails
 
@@ -25,7 +26,7 @@
   (str (:public-url env) "accept-invitation?token=" token))
 
 (defn- application-id-for-email [application]
-  (or (:application/external-id application) (:id application)))
+  (or (:application/external-id application) (:application/id application)))
 
 (defmulti ^:private event-to-emails-impl
   (fn [event _application] (:event/type event)))
@@ -33,12 +34,17 @@
 (defmethod event-to-emails-impl :default [_event _application]
   [])
 
+(defn- applicant-and-members [application]
+  ;; XXX: the tests depend on the order of members
+  (conj (reverse (sort-by :userid (:application/members application)))
+        {:userid (:application/applicant application)}))
+
 ;; There's a slight inconsistency here: we look at current members, so
 ;; a member might get an email for an event that happens before he was
 ;; added.
 (defmethod event-to-emails-impl :application.event/approved [event application]
   (vec
-   (for [member (:members application)] ;; applicant is a member
+   (for [member (applicant-and-members application)]
      {:to-user (:userid member)
       :subject (text :t.email.application-approved/subject)
       :body (text-format :t.email.application-approved/message
@@ -48,7 +54,7 @@
 
 (defmethod event-to-emails-impl :application.event/rejected [event application]
   (vec
-   (for [member (:members application)] ;; applicant is a member
+   (for [member (applicant-and-members application)]
      {:to-user (:userid member)
       :subject (text :t.email.application-rejected/subject)
       :body (text-format :t.email.application-rejected/message
@@ -58,7 +64,7 @@
 
 (defmethod event-to-emails-impl :application.event/closed [event application]
   (vec
-   (for [member (:members application)] ;; applicant is a member
+   (for [member (applicant-and-members application)]
      {:to-user (:userid member)
       :subject (text :t.email.application-closed/subject)
       :body (text-format :t.email.application-closed/message
@@ -90,7 +96,7 @@
 
 (defmethod event-to-emails-impl :application.event/commented [event application]
   (vec
-   (for [handler (get-in application [:workflow :handlers])]
+   (for [handler (get-in application [:application/workflow :workflow.dynamic/handlers])]
      {:to-user handler
       :subject (text :t.email.commented/subject)
       :body (text-format :t.email.commented/message
@@ -101,7 +107,7 @@
 
 (defmethod event-to-emails-impl :application.event/decided [event application]
   (vec
-   (for [handler (get-in application [:workflow :handlers])]
+   (for [handler (get-in application [:application/workflow :workflow.dynamic/handlers])]
      {:to-user handler
       :subject (text :t.email.decided/subject)
       :body (text-format :t.email.decided/message
@@ -130,8 +136,7 @@
 
 (defn event-to-emails [event]
   (when-let [app-id (:application/id event)]
-    ;; TODO use api-get-application-v2 or similar
-    (event-to-emails-impl event (applications/get-application-state app-id))))
+    (event-to-emails-impl event (applications-v2/get-unrestricted-application app-id))))
 
 ;;; Generic poller infrastructure
 
@@ -174,11 +179,11 @@
                                            (send-email! mail))))))
 
 (mount/defstate email-poller
-  :start (doto (java.util.concurrent.ScheduledThreadPoolExecutor. 1)
-           (.scheduleWithFixedDelay run 10 10 java.util.concurrent.TimeUnit/SECONDS))
+  :start (doto (ScheduledThreadPoolExecutor. 1)
+           (.scheduleWithFixedDelay run 10 10 TimeUnit/SECONDS))
   :stop (doto email-poller
           (.shutdown)
-          (.awaitTermination 60 java.util.concurrent.TimeUnit/SECONDS)))
+          (.awaitTermination 60 TimeUnit/SECONDS)))
 
 (comment
   (mount/start #{#'email-poller})
diff --git a/src/clj/rems/poller/entitlements.clj b/src/clj/rems/poller/entitlements.clj
index 0ff1ef952..29ea65922 100644
--- a/src/clj/rems/poller/entitlements.clj
+++ b/src/clj/rems/poller/entitlements.clj
@@ -3,23 +3,25 @@
    entitlements in the db and optionally POSTs them to a webhook."
   (:require [clojure.test :refer :all]
             [mount.core :as mount]
-            [rems.db.applications :as applications]
+            [rems.api.applications-v2 :as applications-v2]
             [rems.db.entitlements :as entitlements]
-            [rems.poller.common :as common]))
+            [rems.poller.common :as common])
+  (:import [java.util.concurrent ScheduledThreadPoolExecutor TimeUnit]))
 
 (defn- entitlements-for-event [event]
   ;; we filter by event here, and by state in update-entitlements-for.
   ;; this is because update-entitlements-for is not actually
   ;; idempotent.
   (when (contains? #{:application.event/approved :application.event/closed} (:event/type event))
-    (entitlements/update-entitlements-for (applications/get-dynamic-application-state (:application/id event)))))
+    (let [application (applications-v2/get-unrestricted-application (:application/id event))]
+      (entitlements/update-entitlements-for application))))
 
 (defn run []
   (common/run-event-poller ::poller entitlements-for-event))
 
 (mount/defstate entitlements-poller
-  :start (doto (java.util.concurrent.ScheduledThreadPoolExecutor. 1)
-           (.scheduleWithFixedDelay run 10 10 java.util.concurrent.TimeUnit/SECONDS))
+  :start (doto (ScheduledThreadPoolExecutor. 1)
+           (.scheduleWithFixedDelay run 10 10 TimeUnit/SECONDS))
   :stop (doto entitlements-poller
           (.shutdown)
-          (.awaitTermination 60 java.util.concurrent.TimeUnit/SECONDS)))
+          (.awaitTermination 60 TimeUnit/SECONDS)))
diff --git a/src/cljs/rems/administration/catalogue_items.cljs b/src/cljs/rems/administration/catalogue_items.cljs
index 505b222d9..310f5aa89 100644
--- a/src/cljs/rems/administration/catalogue_items.cljs
+++ b/src/cljs/rems/administration/catalogue_items.cljs
@@ -111,7 +111,7 @@
   [items language sorting filtering]
   [table/component
    {:column-definitions (catalogue-columns language)
-    :visible-columns [:name :resource :form :workflow :created :end :active :commands]
+    :visible-columns [:name :resource :form :workflow :created :active :commands]
     :sorting sorting
     :filtering filtering
     :id-function :id
diff --git a/src/cljs/rems/administration/forms.cljs b/src/cljs/rems/administration/forms.cljs
index 39d69af69..36cf7bc6e 100644
--- a/src/cljs/rems/administration/forms.cljs
+++ b/src/cljs/rems/administration/forms.cljs
@@ -12,14 +12,12 @@
 (rf/reg-event-fx
  ::enter-page
  (fn [{:keys [db]}]
-   {:db (assoc db ::display-archived? false)
-    :dispatch [::fetch-forms]}))
+   {:dispatch [::fetch-forms]}))
 
 (rf/reg-event-db
  ::fetch-forms
  (fn [db]
-   (fetch "/api/forms/" {:url-params {:archived (::display-archived? db)}
-                         :handler #(rf/dispatch [::fetch-forms-result %])})
+   (fetch "/api/forms/" {:handler #(rf/dispatch [::fetch-forms-result %])})
    (assoc db ::loading? true)))
 
 (rf/reg-event-db
@@ -37,7 +35,8 @@
  (fn [_ [_ item]]
    (put! "/api/forms/update"
          {:params (select-keys item [:id :enabled :archived])
-          :handler (partial status-flags/common-update-handler! #(rf/dispatch [::fetch-forms]))
+          ;; TODO: render the catalogue items that use this form in the error handler
+          :handler (partial status-modal/common-success-handler! #(rf/dispatch [::fetch-forms]))
           :error-handler status-modal/common-error-handler!})
    {}))
 
@@ -53,14 +52,6 @@
 (rf/reg-event-db ::set-filtering (fn [db [_ filtering]] (assoc db ::filtering filtering)))
 (rf/reg-sub ::filtering (fn [db _] (::filtering db)))
 
-(rf/reg-event-fx
- ::set-display-archived?
- (fn [{:keys [db]} [_ display-archived?]]
-   {:db (assoc db ::display-archived? display-archived?)
-    :dispatch [::fetch-forms]}))
-
-(rf/reg-sub ::display-archived? (fn [db _] (::display-archived? db)))
-
 (defn- to-create-form []
   [:a.btn.btn-primary
    {:href "/#/administration/create-form"}
@@ -107,9 +98,6 @@
         (if @(rf/subscribe [::loading?])
           [[spinner/big]]
           [[to-create-form]
-           [status-flags/display-archived-toggle
-            @(rf/subscribe [::display-archived?])
-            #(rf/dispatch [::set-display-archived? %])]
            [forms-list
             @(rf/subscribe [::forms])
             (assoc @(rf/subscribe [::sorting]) :set-sorting #(rf/dispatch [::set-sorting %]))
diff --git a/src/cljs/rems/administration/licenses.cljs b/src/cljs/rems/administration/licenses.cljs
index 892f2a37a..839118e29 100644
--- a/src/cljs/rems/administration/licenses.cljs
+++ b/src/cljs/rems/administration/licenses.cljs
@@ -12,14 +12,12 @@
 (rf/reg-event-fx
  ::enter-page
  (fn [{:keys [db]}]
-   {:db (assoc db ::display-archived? false)
-    :dispatch [::fetch-licenses]}))
+   {:dispatch [::fetch-licenses]}))
 
 (rf/reg-event-db
  ::fetch-licenses
  (fn [db]
-   (fetch "/api/licenses/" {:url-params {:archived (::display-archived? db)}
-                            :handler #(rf/dispatch [::fetch-licenses-result %])})
+   (fetch "/api/licenses/" {:handler #(rf/dispatch [::fetch-licenses-result %])})
    (assoc db ::loading? true)))
 
 (rf/reg-event-db
@@ -39,7 +37,8 @@
  (fn [_ [_ item]]
    (put! "/api/licenses/update"
          {:params (select-keys item [:id :enabled :archived])
-          :handler (partial status-flags/common-update-handler! #(rf/dispatch [::fetch-licenses]))
+          ;; TODO: render the workflows & resources that use this license in the error handler
+          :handler (partial status-modal/common-success-handler! #(rf/dispatch [::fetch-licenses]))
           :error-handler status-modal/common-error-handler!})
    {}))
 
@@ -53,14 +52,6 @@
 (rf/reg-event-db ::set-filtering (fn [db [_ filtering]] (assoc db ::filtering filtering)))
 (rf/reg-sub ::filtering (fn [db _] (::filtering db)))
 
-(rf/reg-event-fx
- ::set-display-archived?
- (fn [{:keys [db]} [_ display-archived?]]
-   {:db (assoc db ::display-archived? display-archived?)
-    :dispatch [::fetch-licenses]}))
-
-(rf/reg-sub ::display-archived? (fn [db _] (::display-archived? db)))
-
 (defn- to-create-licenses []
   [:a.btn.btn-primary
    {:href "/#/administration/create-license"}
@@ -107,9 +98,6 @@
         (if @(rf/subscribe [::loading?])
           [[spinner/big]]
           [[to-create-licenses]
-           [status-flags/display-archived-toggle
-            @(rf/subscribe [::display-archived?])
-            #(rf/dispatch [::set-display-archived? %])]
            [licenses-list
             @(rf/subscribe [::licenses])
             (assoc @(rf/subscribe [::sorting]) :set-sorting #(rf/dispatch [::set-sorting %]))
diff --git a/src/cljs/rems/administration/resources.cljs b/src/cljs/rems/administration/resources.cljs
index fe82db43b..0a92dcfd2 100644
--- a/src/cljs/rems/administration/resources.cljs
+++ b/src/cljs/rems/administration/resources.cljs
@@ -3,7 +3,6 @@
             [rems.administration.administration :refer [administration-navigator-container]]
             [rems.administration.status-flags :as status-flags]
             [rems.atoms :refer [external-link readonly-checkbox]]
-            [rems.catalogue-util :refer [get-catalogue-item-title]]
             [rems.spinner :as spinner]
             [rems.status-modal :as status-modal]
             [rems.table :as table]
@@ -37,10 +36,10 @@
 
 (rf/reg-event-fx
  ::update-resource
- (fn [{:keys [db]} [_ item]]
+ (fn [_ [_ item]]
    (put! "/api/resources/update"
          {:params (select-keys item [:id :enabled :archived])
-          :handler (partial status-flags/common-update-handler! #(rf/dispatch [::fetch-resources]))
+          :handler (partial status-modal/common-success-handler! #(rf/dispatch [::fetch-resources]))
           :error-handler status-modal/common-error-handler!})
    {}))
 
diff --git a/src/cljs/rems/administration/status_flags.cljs b/src/cljs/rems/administration/status_flags.cljs
index 26828d2ed..ec595beb9 100644
--- a/src/cljs/rems/administration/status_flags.cljs
+++ b/src/cljs/rems/administration/status_flags.cljs
@@ -1,8 +1,5 @@
 (ns rems.administration.status-flags
-  (:require [re-frame.core :as rf]
-            [rems.status-modal :as status-modal]
-            [rems.text :refer [text]]
-            [rems.catalogue-util :refer [get-catalogue-item-title]]))
+  (:require [rems.text :refer [text]]))
 
 (defn- disable-button [item on-change]
   [:button.btn.btn-secondary.button-min-width
@@ -48,33 +45,3 @@
                              :on-change #(on-change (not display-archived?))}]
    [:label.form-check-label {:for "display-archived"}
     (text :t.administration/display-archived)]])
-
-(defn- format-update-error [{:keys [type catalogue-items resources workflows]}]
-  (let [language @(rf/subscribe [:language])]
-    [:p (text type)
-     (into [:ul]
-           (for [ci catalogue-items]
-             ;; TODO open in new tab?
-             [:li
-              (text :t.administration/catalogue-item) ": "
-              [:a {:href (str "#/administration/catalogue-items/" (:id ci))}
-               (get-catalogue-item-title ci language)]]))
-     (into [:ul]
-           (for [r resources]
-             [:li
-              (text :t.administration/resource) ": "
-              [:a {:href (str "#/administration/resources/" (:id r))} (:resid r)]]))
-     (into [:ul]
-           (for [w workflows]
-             [:li
-              (text :t.administration/workflow) ": "
-              [:a {:href (str "#/administration/workflows/" (:id w))} (:title w)]]))]))
-
-(defn- format-update-failure [{:keys [errors]}]
-  (into [:div]
-        (map format-update-error errors)))
-
-(defn common-update-handler! [on-close response]
-  (if (:success response)
-    (status-modal/set-success! {:on-close on-close})
-    (status-modal/set-error! {:error-content (format-update-failure response)})))
diff --git a/src/cljs/rems/administration/workflows.cljs b/src/cljs/rems/administration/workflows.cljs
index e295fdd13..87d5e2e67 100644
--- a/src/cljs/rems/administration/workflows.cljs
+++ b/src/cljs/rems/administration/workflows.cljs
@@ -12,14 +12,12 @@
 (rf/reg-event-fx
  ::enter-page
  (fn [{:keys [db]}]
-   {:db (assoc db ::display-archived? false)
-    :dispatch [::fetch-workflows]}))
+   {:dispatch [::fetch-workflows]}))
 
 (rf/reg-event-db
  ::fetch-workflows
  (fn [db]
-   (fetch "/api/workflows/" {:url-params {:archived (::display-archived? db)}
-                             :handler #(rf/dispatch [::fetch-workflows-result %])})
+   (fetch "/api/workflows/" {:handler #(rf/dispatch [::fetch-workflows-result %])})
    (assoc db ::loading? true)))
 
 (rf/reg-event-db
@@ -37,7 +35,8 @@
  (fn [_ [_ item]]
    (put! "/api/workflows/update"
          {:params (select-keys item [:id :enabled :archived])
-          :handler (partial status-flags/common-update-handler! #(rf/dispatch [::fetch-workflows]))
+          ;; TODO: render the catalogue items that use this workflow in the error handler
+          :handler (partial status-modal/common-success-handler! #(rf/dispatch [::fetch-workflows]))
           :error-handler status-modal/common-error-handler!})
    {}))
 
@@ -53,14 +52,6 @@
 (rf/reg-event-db ::set-filtering (fn [db [_ filtering]] (assoc db ::filtering filtering)))
 (rf/reg-sub ::filtering (fn [db _] (::filtering db)))
 
-(rf/reg-event-fx
- ::set-display-archived?
- (fn [{:keys [db]} [_ display-archived?]]
-   {:db (assoc db ::display-archived? display-archived?)
-    :dispatch [::fetch-workflows]}))
-(rf/reg-sub ::display-archived? (fn [db _] (::display-archived? db)))
-
-
 (defn- to-create-workflow []
   [:a.btn.btn-primary
    {:href "/#/administration/create-workflow"}
@@ -108,9 +99,6 @@
         (if @(rf/subscribe [::loading?])
           [[spinner/big]]
           [[to-create-workflow]
-           [status-flags/display-archived-toggle
-            @(rf/subscribe [::display-archived?])
-            #(rf/dispatch [::set-display-archived? %])]
            [workflows-list
             @(rf/subscribe [::workflows])
             (assoc @(rf/subscribe [::sorting]) :set-sorting #(rf/dispatch [::set-sorting %]))
diff --git a/src/cljs/rems/catalogue.cljs b/src/cljs/rems/catalogue.cljs
index 5427e898e..c29b27a92 100644
--- a/src/cljs/rems/catalogue.cljs
+++ b/src/cljs/rems/catalogue.cljs
@@ -89,11 +89,6 @@
               :sortable? false
               :filterable? false}})
 
-(defn- catalogue-item-visible? [item]
-  ;; archived items are not returned from the API
-  (and (:enabled item)
-       (:active item)))
-
 (defn- catalogue-list
   "Renders the catalogue using table.
 
@@ -103,7 +98,7 @@
    (merge {:column-definitions (catalogue-columns language config)
            :visible-columns [:name :commands]
            :id-function :id
-           :items (filter catalogue-item-visible? items)
+           :items (filter :enabled items)
            :class "catalogue"}
           (when sorting {:sorting sorting})
           (when filtering {:filtering filtering}))])


Test patch:
diff --git a/src/clj/rems/db/test_data.clj b/src/clj/rems/db/test_data.clj
index 45129ab03..8684babfb 100644
--- a/src/clj/rems/db/test_data.clj
+++ b/src/clj/rems/db/test_data.clj
@@ -672,11 +672,7 @@
                                                      {"en" "Dynamic workflow (disabled)"
                                                       "fi" "Dynaaminen työvuo (pois käytöstä)"})]
         (create-disabled-applications! dynamic-disabled (:dynamic workflows) (+fake-users+ :approver1) (+fake-users+ :approver1))
-        (db/set-catalogue-item-state! {:id dynamic-disabled :enabled false}))
-      (let [dynamic-expired (create-catalogue-item! res1 (:dynamic workflows) form
-                                                    {"en" "Dynamic workflow (expired)"
-                                                     "fi" "Dynaaminen työvuo (vanhentunut)"})]
-        (db/set-catalogue-item-state! {:id dynamic-expired :endt (time/now)})))
+        (db/set-catalogue-item-state! {:id dynamic-disabled :enabled false})))
     (finally
       (DateTimeUtils/setCurrentMillisSystem))))
 
diff --git a/test/clj/rems/api/test_administration.clj b/test/clj/rems/api/test_administration.clj
index 05e6c07b9..556d34fbc 100644
--- a/test/clj/rems/api/test_administration.clj
+++ b/test/clj/rems/api/test_administration.clj
@@ -48,8 +48,7 @@
       (let [resp (api-call :put "/api/resources/update" {:id resource-id :enabled true :archived true}
                            api-key user-id)]
         (is (false? (:success resp)))
-        (is (= [{:type "t.administration.errors/resource-in-use"
-                 :catalogue-items [{:id catalogue-id :title "test-item-title" :localizations nil}]}]
+        (is (= [{:type "t.administration.errors/resource-in-use" :catalogue-items [catalogue-id]}]
                (:errors resp)))))
 
     (testing "can disable a form"
@@ -59,8 +58,7 @@
       (let [resp (api-call :put "/api/forms/update" {:id form-id :enabled true :archived true}
                            api-key user-id)]
         (is (false? (:success resp)))
-        (is (= [{:type "t.administration.errors/form-in-use"
-                 :catalogue-items [{:id catalogue-id :title "test-item-title" :localizations nil}]}]
+        (is (= [{:type "t.administration.errors/form-in-use" :catalogue-items [catalogue-id]}]
                (:errors resp)))))
 
     (testing "can disable a workflow"
@@ -70,8 +68,7 @@
       (let [resp (api-call :put "/api/workflows/update" {:id workflow-id :enabled true :archived true}
                            api-key user-id)]
         (is (false? (:success resp)))
-        (is (= [{:type "t.administration.errors/workflow-in-use"
-                 :catalogue-items [{:id catalogue-id :title "test-item-title" :localizations nil}]}]
+        (is (= [{:type "t.administration.errors/workflow-in-use" :catalogue-items [catalogue-id]}]
                (:errors resp)))))
 
     (testing "can disable a license"
@@ -81,9 +78,7 @@
       (let [resp (api-call :put "/api/licenses/update" {:id license-id :enabled true :archived true}
                            api-key user-id)]
         (is (false? (:success resp)))
-        (is (= [{:type "t.administration.errors/license-in-use"
-                 :resources [{:id resource-id :resid "test"}]
-                 :workflows [{:id workflow-id :title "dynamic workflow"}]}]
+        (is (= [{:type "t.administration.errors/license-in-use" :resources [resource-id] :workflows [workflow-id]}]
                (:errors resp)))))
 
     (testing "can archive a catalogue item"
diff --git a/test/clj/rems/api/test_applications.clj b/test/clj/rems/api/test_applications.clj
index 2015327d1..413331cf9 100644
--- a/test/clj/rems/api/test_applications.clj
+++ b/test/clj/rems/api/test_applications.clj
@@ -347,19 +347,20 @@
         cat-id (create-catalogue-item form-id workflow-id)
         app-id (create-v2-application [cat-id] user-id)]
     (testing "uploading attachment for a draft"
-      (-> (request :post (str "/api/applications/add_attachment?application-id=" app-id "&field-id=" field-id))
-          (assoc :params {"file" filecontent})
-          (assoc :multipart-params {"file" filecontent})
-          (authenticate api-key user-id)
-          handler
-          assert-response-is-ok))
+      (let [body (-> (request :post (str "/api/applications/add_attachment?application-id=" app-id "&field-id=" field-id))
+                     (assoc :params {"file" filecontent})
+                     (assoc :multipart-params {"file" filecontent})
+                     (authenticate api-key user-id)
+                     handler
+                     read-ok-body)]
+        (is (= {:success true} body))))
     (testing "uploading malicious file for a draft"
       (let [response (-> (request :post (str "/api/applications/add_attachment?application-id=" app-id "&field-id=" field-id))
                          (assoc :params {"file" malicious-content})
                          (assoc :multipart-params {"file" malicious-content})
                          (authenticate api-key user-id)
                          handler)]
-        (is (= 400 (:status response)))))
+        (is (response-is-bad-request? response))))
     (testing "retrieving attachment for a draft"
       (let [response (-> (request :get "/api/applications/attachments" {:application-id app-id :field-id field-id})
                          (authenticate api-key user-id)
diff --git a/test/clj/rems/api/test_catalogue_items.clj b/test/clj/rems/api/test_catalogue_items.clj
index 7908c5dfa..d2d60eb09 100644
--- a/test/clj/rems/api/test_catalogue_items.clj
+++ b/test/clj/rems/api/test_catalogue_items.clj
@@ -27,7 +27,7 @@
                                :archived true})
                    handler
                    read-body)]
-      (is (= 13 (:id data))))
+      (is (= 12 (:id data))))
     (let [data (-> (request :get "/api/catalogue-items/7")
                    (authenticate api-key user-id)
                    handler
diff --git a/test/clj/rems/api/test_public.clj b/test/clj/rems/api/test_public.clj
index 9c1a629ee..fd6bbf88d 100644
--- a/test/clj/rems/api/test_public.clj
+++ b/test/clj/rems/api/test_public.clj
@@ -1,9 +1,9 @@
 (ns ^:integration rems.api.test-public
   (:require [clojure.test :refer :all]
-            [rems.db.roles :as roles]
+            [rems.api.testing :refer :all]
             [rems.db.users :as users]
             [rems.handler :refer :all]
-            [rems.api.testing :refer :all]
+            [rems.poller.entitlements]
             [ring.mock.request :refer :all]))
 
 (use-fixtures
@@ -21,6 +21,7 @@
       (is (= [:en :fi] (sort languages))))))
 
 (deftest entitlements-test
+  (rems.poller.entitlements/run)
   (let [api-key "42"]
     (testing "all"
       (let [data (-> (request :get "/api/entitlements")
diff --git a/test/clj/rems/api/test_workflows.clj b/test/clj/rems/api/test_workflows.clj
index 3a00e0b65..e90323285 100644
--- a/test/clj/rems/api/test_workflows.clj
+++ b/test/clj/rems/api/test_workflows.clj
@@ -87,7 +87,7 @@
             (authenticate "42" "owner")
             handler
             assert-response-is-ok)
-        (let [workflows (-> (request :get "/api/workflows" {:archived true})
+        (let [workflows (-> (request :get "/api/workflows")
                             (authenticate "42" "owner")
                             handler
                             assert-response-is-ok
diff --git a/test/clj/rems/api/testing.clj b/test/clj/rems/api/testing.clj
index 257af4c30..8b9e8f9f9 100644
--- a/test/clj/rems/api/testing.clj
+++ b/test/clj/rems/api/testing.clj
@@ -40,6 +40,9 @@
 (defn assert-response-is-server-error? [response]
   (assert (= 500 (:status response))))
 
+(defn response-is-bad-request? [response]
+  (= 400 (:status response)))
+
 (defn response-is-unauthorized? [response]
   (= 401 (:status response)))
 
diff --git a/test/clj/rems/application/test_model.clj b/test/clj/rems/application/test_model.clj
index 18410f247..4178c9e04 100644
--- a/test/clj/rems/application/test_model.clj
+++ b/test/clj/rems/application/test_model.clj
@@ -48,7 +48,6 @@
                                              :end nil
                                              :enabled true
                                              :archived false
-                                             :active true
                                              :state "enabled"}
                                          20 {:id 20
                                              :resource-id 21
@@ -66,7 +65,6 @@
                                              :end nil
                                              :enabled true
                                              :archived false
-                                             :active true
                                              :state "enabled"}}
 
                     :get-license {30 {:id 30
@@ -80,7 +78,6 @@
                                       :start (DateTime. 100)
                                       :end nil
                                       :enabled true
-                                      :active true
                                       :archived false}
                                   31 {:id 31
                                       :licensetype "text"
@@ -93,7 +90,6 @@
                                       :start (DateTime. 100)
                                       :end nil
                                       :enabled true
-                                      :active true
                                       :archived false}
                                   32 {:id 32
                                       :licensetype "attachment"
@@ -109,7 +105,6 @@
                                       :start (DateTime. 100)
                                       :end nil
                                       :enabled true
-                                      :active true
                                       :archived false}}
 
                     :get-user {"applicant" {:eppn "applicant"
@@ -162,7 +157,6 @@
                                                            :catalogue-item/start (DateTime. 100)
                                                            :catalogue-item/end nil
                                                            :catalogue-item/enabled true
-                                                           :catalogue-item/active true
                                                            :catalogue-item/archived false}
                                                           {:catalogue-item/id 20
                                                            :resource/id 21
@@ -173,7 +167,6 @@
                                                            :catalogue-item/start (DateTime. 100)
                                                            :catalogue-item/end nil
                                                            :catalogue-item/enabled true
-                                                           :catalogue-item/active true
                                                            :catalogue-item/archived false}]
                                   :application/licenses [{:license/id 30
                                                           :license/accepted false
@@ -186,7 +179,6 @@
                                                                          :default "http://non-localized-license-link"}
                                                           :license/start (DateTime. 100)
                                                           :license/end nil
-                                                          :license/active true
                                                           :license/enabled true
                                                           :license/archived false}
                                                          {:license/id 31
@@ -200,7 +192,6 @@
                                                                          :default "non-localized license text"}
                                                           :license/start (DateTime. 100)
                                                           :license/end nil
-                                                          :license/active true
                                                           :license/enabled true
                                                           :license/archived false}
                                                          {:license/id 32
@@ -217,7 +208,6 @@
                                                                                         :default "non-localized filename"}
                                                           :license/start (DateTime. 100)
                                                           :license/end nil
-                                                          :license/active true
                                                           :license/enabled true
                                                           :license/archived false}]
                                   :application/accepted-licenses {"applicant" #{}}
diff --git a/test/clj/rems/test_db.clj b/test/clj/rems/test_db.clj
index 9d284e573..43e500747 100644
--- a/test/clj/rems/test_db.clj
+++ b/test/clj/rems/test_db.clj
@@ -8,7 +8,7 @@
             [conman.core :as conman]
             [luminus-migrations.core :as migrations]
             [mount.core :as mount]
-            [rems.auth.NotAuthorizedException]
+            [rems.api.applications-v2 :as applications-v2]
             [rems.auth.ForbiddenException]
             [rems.config :refer [env]]
             [rems.context :as context]
@@ -20,11 +20,11 @@
             [rems.db.test-data :as test-data]
             [rems.db.users :as users]
             [rems.db.workflow-actors :as actors]
-            rems.poller.entitlements
+            [rems.poller.entitlements]
             [rems.testing-tempura :refer [fake-tempura-fixture]]
             [rems.util :refer [get-user-id]]
             [stub-http.core :as stub])
-  (:import (rems.auth ForbiddenException NotAuthorizedException)))
+  (:import (rems.auth ForbiddenException)))
 
 (defn db-once-fixture [f]
   (fake-tempura-fixture
@@ -56,7 +56,7 @@
       (is (= ["B" "ELFA Corpus"] (sort (map :title (db/get-catalogue-items))))
           "should find the two items")
       (let [item-from-list (second (db/get-catalogue-items))
-            item-by-id (first (db/get-catalogue-items {:ids [(:id item-from-list)]}))]
+            item-by-id (db/get-catalogue-item {:id (:id item-from-list)})]
         (is (= item-from-list (dissoc item-by-id
                                       :resource-name
                                       :form-name
@@ -217,10 +217,10 @@
                                       :application-id app-id
                                       :time (time/now)
                                       :comment ""})))
-    (is (= :application.state/approved (:state (applications/get-dynamic-application-state app-id))))
+    (is (= :application.state/approved (:application/state (applications-v2/api-get-application-v2 uid app-id))))
 
-    ;; TODO: entitlements are not tracked for dynamic applications
-    (is (= [] #_["resid111" "resid222"] (sort (map :resid (db/get-entitlements {:application app-id}))))
+    (rems.poller.entitlements/run)
+    (is (= ["resid111" "resid222"] (sort (map :resid (db/get-entitlements {:application app-id}))))
         "should create entitlements for both resources")))
 
 (deftest test-phases
@@ -420,10 +420,6 @@
         (applications/approve-application "event-test-approver" app 1 "c2")
         (is (= {:curround 1 :state "approved"} (fetch app)))
 
-        (is (= [{:catappid app :resid nil :userid uid}]
-               (map #(select-keys % [:catappid :resid :userid])
-                    (db/get-entitlements))))
-
         (is (= (->> (applications/get-application-state app)
                     :events
                     (map #(select-keys % [:round :event :comment])))
@@ -545,10 +541,7 @@
                     (map #(select-keys % [:round :event])))
                [{:round 0 :event "apply"}
                 {:round 0 :event "autoapprove"}
-                {:round 1 :event "autoapprove"}]))
-        (is (contains? (set (map #(select-keys % [:catappid :resid :userid])
-                                 (db/get-entitlements)))
-                       {:catappid auto-app :resid "ABC" :userid uid}))))
+                {:round 1 :event "autoapprove"}]))))
     (let [new-wf (:id (db/create-workflow! {:organization "abc" :modifieruserid uid :owneruserid uid :title "3rd party review workflow" :fnlround 0}))
           new-item (:id (db/create-catalogue-item! {:title "A" :form nil :resid nil :wfid new-wf}))]
       (actors/add-approver! new-wf uid 0)
@@ -644,21 +637,38 @@
                   {:round 0 :event "return" :comment "returning"}])))))))
 
 (deftest test-get-entitlements-for-export
+  (db/add-user! {:user "handler" :userattrs nil})
   (db/add-user! {:user "jack" :userattrs nil})
   (db/add-user! {:user "jill" :userattrs nil})
-  (let [wf (:id (db/create-workflow! {:organization "abc" :modifieruserid "owner" :owneruserid "owner" :title "Test workflow" :fnlround 1}))
+  (let [workflow {:type :workflow/dynamic :handlers ["handler"]}
+        wf (:id (db/create-workflow! {:organization "abc" :modifieruserid "owner" :owneruserid "owner" :title "dynamic" :fnlround -1 :workflow (cheshire/generate-string workflow)}))
+        formid (:id (db/create-form! {:organization "abc" :title "internal-title" :user "owner"}))
         res1 (:id (db/create-resource! {:resid "resource1" :organization "pre" :owneruserid "owner" :modifieruserid "owner"}))
         res2 (:id (db/create-resource! {:resid "resource2" :organization "pre" :owneruserid "owner" :modifieruserid "owner"}))
-        item1 (:id (db/create-catalogue-item! {:title "item1" :form nil :resid res1 :wfid wf}))
-        item2 (:id (db/create-catalogue-item! {:title "item2" :form nil :resid res2 :wfid wf}))
-        jack-app (applications/create-new-draft "jack" wf)
-        jill-app (applications/create-new-draft "jill" wf)]
-    (db/add-application-item! {:application jack-app :item item1})
-    (db/add-application-item! {:application jill-app :item item1})
-    (db/add-application-item! {:application jill-app :item item2})
-    (applications/submit-application "jack" jack-app)
-    (applications/submit-application "jill" jill-app)
-    ;; entitlements should now be added via autoapprove
+        item1 (:id (db/create-catalogue-item! {:title "item1" :form formid :resid res1 :wfid wf}))
+        item2 (:id (db/create-catalogue-item! {:title "item2" :form formid :resid res2 :wfid wf}))
+        jack-app (:application-id (applications/create-application! "jack" [item1]))
+        jill-app (:application-id (applications/create-application! "jill" [item1 item2]))]
+    (assert (nil? (applications/command! {:type :application.command/submit
+                                          :time (time/now)
+                                          :actor "jack"
+                                          :application-id jack-app})))
+    (assert (nil? (applications/command! {:type :application.command/approve
+                                          :time (time/now)
+                                          :actor "handler"
+                                          :application-id jack-app
+                                          :comment ""})))
+    (assert (nil? (applications/command! {:type :application.command/submit
+                                          :time (time/now)
+                                          :actor "jill"
+                                          :application-id jill-app})))
+    (assert (nil? (applications/command! {:type :application.command/approve
+                                          :time (time/now)
+                                          :actor "handler"
+                                          :application-id jill-app
+                                          :comment ""})))
+    (rems.poller.entitlements/run)
+
     (binding [context/*roles* #{:handler}]
       (let [lines (split-lines (entitlements/get-entitlements-for-export))]
         (is (= 4 (count lines))) ;; header + 3 resources
@@ -696,13 +706,7 @@
             item2 (:id (db/create-catalogue-item! {:title "item2" :form formid :resid res2 :wfid wfid}))]
         (db/add-user! {:user uid :userattrs (cheshire/generate-string {"mail" "b@o.b"})})
         (db/add-user! {:user admin :userattrs nil})
-        (let [app-id (applications/create-new-draft uid wfid)]
-          (db/add-application-item! {:application app-id :item item1})
-          (db/add-application-item! {:application app-id :item item2})
-          (applications/add-application-created-event! {:application-id app-id
-                                                        :catalogue-item-ids [item1 item2]
-                                                        :time (time/now)
-                                                        :actor uid})
+        (let [app-id (:application-id (applications/create-application! uid [item1 item2]))]
           (is (nil? (applications/command! {:type :application.command/submit
                                             :actor uid
                                             :application-id app-id
@@ -753,68 +757,6 @@
                         {"resource" "resource2" "application" app-id "user" "bob" "mail" "b@o.b"}]
                        body))))))))))
 
-(deftest test-dynamic-workflow
-  (db/add-user! {:user "alice" :userattrs "{}"})
-  (db/add-user! {:user "bob" :userattrs "{}"})
-  (db/add-user! {:user "handler" :userattrs "{}"})
-  (let [workflow {:type :workflow/dynamic
-                  :handlers ["handler"]}
-        wfid (:id (db/create-workflow! {:organization "abc" :modifieruserid "owner" :owneruserid "owner" :title "dynamic" :fnlround -1 :workflow (cheshire/generate-string workflow)}))
-        form (:id (db/create-form! {:organization "abc" :title "internal-title" :user "owner"}))
-        form-item (:id (db/create-form-item! {:type "text" :user "owner" :value 0}))
-        _ (db/link-form-item! {:form form :itemorder 1 :item form-item :user "owner" :optional false})
-        res (:id (db/create-resource! {:resid "some resource" :organization "abc" :owneruserid "owner" :modifieruserid "owner"}))
-        ci (:id (db/create-catalogue-item! {:title "dynamic" :resid res :wfid wfid :form form}))
-        app-id (applications/create-new-draft "alice" wfid)]
-    (db/add-application-item! {:application app-id :item ci})
-    (db/save-field-value! {:application app-id :form form :item form-item :user "alice" :value "X"})
-    ;; TODO: rewrite these tests to be event-based; remove the above stuff
-    (applications/add-application-created-event! {:application-id app-id
-                                                  :catalogue-item-ids [ci]
-                                                  :time (time/now)
-                                                  :actor "alice"})
-
-    (is (= {:applicantuserid "alice"
-            :state :application.state/draft
-            :workflow workflow}
-           (select-keys (applications/get-dynamic-application-state app-id) [:applicantuserid :state :workflow])))
-    (is (nil? (applications/command! {:type :application.command/invite-member
-                                      :actor "alice"
-                                      :member {:name "Jane Doe" :email "jane.doe@members.com"}
-                                      :application-id app-id
-                                      :time (time/now)})))
-    (is (nil? (applications/command! {:type :application.command/save-draft
-                                      :actor "alice"
-                                      :application-id app-id
-                                      :time (time/now)
-                                      :field-values {form-item "X"}
-                                      :accepted-licenses #{}})))
-    (is (nil? (applications/command! {:type :application.command/submit
-                                      :actor "alice"
-                                      :application-id app-id
-                                      :time (time/now)})))
-    (is (= :application.state/submitted
-           (:state (applications/get-dynamic-application-state app-id))))
-    (is (nil? (applications/command! {:type :application.command/add-member
-                                      :actor "handler"
-                                      :member {:userid "bob"}
-                                      :application-id app-id
-                                      :time (time/now)})))
-    (is (nil? (applications/command! {:type :application.command/invite-member
-                                      :actor "handler"
-                                      :member {:name "John Doe" :email "john.doe@members.com"}
-                                      :application-id app-id
-                                      :time (time/now)})))
-    (is (= [{:userid "alice"} {:userid "bob"}]
-           (:members (applications/get-dynamic-application-state app-id))))
-    (is (nil? (applications/command! {:type :application.command/approve
-                                      :actor "handler"
-                                      :application-id app-id
-                                      :time (time/now)
-                                      :comment ""})))
-    (is (= :application.state/approved
-           (:state (applications/get-dynamic-application-state app-id))))))
-
 (deftest test-create-demo-data!
   ;; just a smoke test, check that create-demo-data doesn't fail
   (test-data/create-demo-data!)