Repository: CSCfi/rems
Pull request #1687 (2019-10-07 10:40:52)

resource blacklist API
for #1682

# Definition of Done / Review checklist

## Reviewability
- [x] link to issue

## API
- [x] API is documented and shows up in Swagger UI
- [x] API is backwards compatible or completely new
- [x] Events are backwards compatible

## Documentation
- [x] update changelog if necessary (will update once feature is complete)

## Testing
- [x] complex logic is unit tested
- [x] valuable features are integration / browser / acceptance tested automatically

## Follow-up
- [ ] no critical TODOs left to implement



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e2e906843..014989e8f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -66,8 +66,6 @@ Changes since v2.4
 - Show separately for each license if it has been accepted by the member (#1591)
 - Show all errors preventing application submission at the same time (#1594)
 - Show applicant's previous applications to handler (#1653)
-- Handler can close an application whenever after initial submission (#1669)
-- Documentation about [user permissions by application state](docs/application-permissions.md)
 
 ### Enhancements
 - Improved version information in footer
@@ -81,14 +79,12 @@ Changes since v2.4
 - Show three latest events as a default on the application page (#1632)
 - A change of language persists now after login thanks to a new language setting cookie.
 - A returning user will see the login screen in the correct language if he or she has the cookie.
-- Event descriptions on application page now use full name and are more thorough (#1634)
 
 ### Fixes
 - Entitlement API
 - Search on the catalogue and admin pages did not support multiple search terms (#1541)
 - Hide flash message when changing language so mixed language content is not shown
 - Printing application pages now works (except for drafts) (#1643)
-- Applicant can now view attachment licenses (#1676)
 
 ## v2.5 "Maarintie" 2019-07-18
 
diff --git a/docs/application-permissions.md b/docs/application-permissions.md
deleted file mode 100644
index 00ad504d9..000000000
--- a/docs/application-permissions.md
+++ /dev/null
@@ -1,385 +0,0 @@
-# Application Permissions Reference
-
-<table border="1">
-    <tr>
-        <th>State \ Role</th>
-        <th>applicant</th>
-        <th>commenter</th>
-        <th>decider</th>
-        <th>everyone‑else</th>
-        <th>handler</th>
-        <th>member</th>
-        <th>past‑commenter</th>
-        <th>past‑decider</th>
-        <th>reporter</th>
-    </tr>
-    <tr>
-        <th valign="top">draft</th>
-        <td valign="top">
-            <!-- role: applicant -->
-            <div>accept‑licenses</div>
-            <div>change‑resources</div>
-            <div>close</div>
-            <div>copy‑as‑new</div>
-            <div>invite‑member</div>
-            <div>remove‑member</div>
-            <div>save‑draft</div>
-            <div>submit</div>
-            <div>uninvite‑member</div>
-        </td>
-        <td valign="top">
-            <!-- role: commenter -->
-        </td>
-        <td valign="top">
-            <!-- role: decider -->
-        </td>
-        <td valign="top">
-            <!-- role: everyone-else -->
-            <div>accept‑invitation</div>
-        </td>
-        <td valign="top">
-            <!-- role: handler -->
-        </td>
-        <td valign="top">
-            <!-- role: member -->
-            <div>accept‑licenses</div>
-            <div>copy‑as‑new</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-commenter -->
-        </td>
-        <td valign="top">
-            <!-- role: past-decider -->
-        </td>
-        <td valign="top">
-            <!-- role: reporter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-    </tr>
-    <tr>
-        <th valign="top">submitted</th>
-        <td valign="top">
-            <!-- role: applicant -->
-            <div>accept‑licenses</div>
-            <div>copy‑as‑new</div>
-            <div>remove‑member</div>
-            <div>uninvite‑member</div>
-        </td>
-        <td valign="top">
-            <!-- role: commenter -->
-            <div>see‑everything</div>
-            <div>comment</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: decider -->
-            <div>see‑everything</div>
-            <div>decide</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: everyone-else -->
-            <div>accept‑invitation</div>
-        </td>
-        <td valign="top">
-            <!-- role: handler -->
-            <div>see‑everything</div>
-            <div>add‑licenses</div>
-            <div>add‑member</div>
-            <div>approve</div>
-            <div>change‑resources</div>
-            <div>close</div>
-            <div>invite‑member</div>
-            <div>reject</div>
-            <div>remark</div>
-            <div>remove‑member</div>
-            <div>request‑comment</div>
-            <div>request‑decision</div>
-            <div>return</div>
-            <div>uninvite‑member</div>
-        </td>
-        <td valign="top">
-            <!-- role: member -->
-            <div>accept‑licenses</div>
-            <div>copy‑as‑new</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-commenter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-decider -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: reporter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-    </tr>
-    <tr>
-        <th valign="top">returned</th>
-        <td valign="top">
-            <!-- role: applicant -->
-            <div>accept‑licenses</div>
-            <div>change‑resources</div>
-            <div>close</div>
-            <div>copy‑as‑new</div>
-            <div>invite‑member</div>
-            <div>remove‑member</div>
-            <div>save‑draft</div>
-            <div>submit</div>
-            <div>uninvite‑member</div>
-        </td>
-        <td valign="top">
-            <!-- role: commenter -->
-            <div>see‑everything</div>
-            <div>comment</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: decider -->
-            <div>see‑everything</div>
-            <div>decide</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: everyone-else -->
-            <div>accept‑invitation</div>
-        </td>
-        <td valign="top">
-            <!-- role: handler -->
-            <div>see‑everything</div>
-            <div>add‑licenses</div>
-            <div>add‑member</div>
-            <div>change‑resources</div>
-            <div>close</div>
-            <div>invite‑member</div>
-            <div>remark</div>
-            <div>remove‑member</div>
-            <div>request‑comment</div>
-            <div>uninvite‑member</div>
-        </td>
-        <td valign="top">
-            <!-- role: member -->
-            <div>accept‑licenses</div>
-            <div>copy‑as‑new</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-commenter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-decider -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: reporter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-    </tr>
-    <tr>
-        <th valign="top">approved</th>
-        <td valign="top">
-            <!-- role: applicant -->
-            <div>accept‑licenses</div>
-            <div>copy‑as‑new</div>
-            <div>remove‑member</div>
-            <div>uninvite‑member</div>
-        </td>
-        <td valign="top">
-            <!-- role: commenter -->
-            <div>see‑everything</div>
-            <div>comment</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: decider -->
-            <div>see‑everything</div>
-            <div>decide</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: everyone-else -->
-            <div>accept‑invitation</div>
-        </td>
-        <td valign="top">
-            <!-- role: handler -->
-            <div>see‑everything</div>
-            <div>add‑member</div>
-            <div>change‑resources</div>
-            <div>close</div>
-            <div>invite‑member</div>
-            <div>remark</div>
-            <div>remove‑member</div>
-            <div>revoke</div>
-            <div>uninvite‑member</div>
-        </td>
-        <td valign="top">
-            <!-- role: member -->
-            <div>accept‑licenses</div>
-            <div>copy‑as‑new</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-commenter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-decider -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: reporter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-    </tr>
-    <tr>
-        <th valign="top">closed</th>
-        <td valign="top">
-            <!-- role: applicant -->
-            <div>copy‑as‑new</div>
-        </td>
-        <td valign="top">
-            <!-- role: commenter -->
-            <div>see‑everything</div>
-        </td>
-        <td valign="top">
-            <!-- role: decider -->
-            <div>see‑everything</div>
-        </td>
-        <td valign="top">
-            <!-- role: everyone-else -->
-        </td>
-        <td valign="top">
-            <!-- role: handler -->
-            <div>see‑everything</div>
-        </td>
-        <td valign="top">
-            <!-- role: member -->
-            <div>copy‑as‑new</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-commenter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-decider -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: reporter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-    </tr>
-    <tr>
-        <th valign="top">revoked</th>
-        <td valign="top">
-            <!-- role: applicant -->
-            <div>accept‑licenses</div>
-            <div>copy‑as‑new</div>
-            <div>remove‑member</div>
-            <div>uninvite‑member</div>
-        </td>
-        <td valign="top">
-            <!-- role: commenter -->
-            <div>see‑everything</div>
-            <div>comment</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: decider -->
-            <div>see‑everything</div>
-            <div>decide</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: everyone-else -->
-            <div>accept‑invitation</div>
-        </td>
-        <td valign="top">
-            <!-- role: handler -->
-            <div>see‑everything</div>
-            <div>add‑member</div>
-            <div>change‑resources</div>
-            <div>close</div>
-            <div>invite‑member</div>
-            <div>remark</div>
-            <div>remove‑member</div>
-            <div>revoke</div>
-            <div>uninvite‑member</div>
-        </td>
-        <td valign="top">
-            <!-- role: member -->
-            <div>accept‑licenses</div>
-            <div>copy‑as‑new</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-commenter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-decider -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: reporter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-    </tr>
-    <tr>
-        <th valign="top">rejected</th>
-        <td valign="top">
-            <!-- role: applicant -->
-            <div>copy‑as‑new</div>
-        </td>
-        <td valign="top">
-            <!-- role: commenter -->
-            <div>see‑everything</div>
-        </td>
-        <td valign="top">
-            <!-- role: decider -->
-            <div>see‑everything</div>
-        </td>
-        <td valign="top">
-            <!-- role: everyone-else -->
-        </td>
-        <td valign="top">
-            <!-- role: handler -->
-            <div>see‑everything</div>
-        </td>
-        <td valign="top">
-            <!-- role: member -->
-            <div>copy‑as‑new</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-commenter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: past-decider -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-        <td valign="top">
-            <!-- role: reporter -->
-            <div>see‑everything</div>
-            <div>remark</div>
-        </td>
-    </tr>
-</table>
\ No newline at end of file
diff --git a/project.clj b/project.clj
index 652d11609..36954c79a 100644
--- a/project.clj
+++ b/project.clj
@@ -62,7 +62,6 @@
                  [org.webjars/font-awesome "5.10.1"]
                  [org.webjars/jquery "3.4.1"]
                  [prismatic/schema-generators "0.1.3"]
-                 [px0/beautify-web "0.1.1"]
                  [re-frame "0.10.8"]
                  [reagent "0.9.0-rc1"] ; includes React 16.9 support https://github.com/reagent-project/reagent/pull/443
                  [reagent-utils "0.3.3"]
diff --git a/resources/migrations/20191007092006-blacklist.down.sql b/resources/migrations/20191007092006-blacklist.down.sql
new file mode 100644
index 000000000..e60f58a4d
--- /dev/null
+++ b/resources/migrations/20191007092006-blacklist.down.sql
@@ -0,0 +1,1 @@
+DROP TABLE blacklist_event;
diff --git a/resources/migrations/20191007092006-blacklist.up.sql b/resources/migrations/20191007092006-blacklist.up.sql
new file mode 100644
index 000000000..d2b7a8975
--- /dev/null
+++ b/resources/migrations/20191007092006-blacklist.up.sql
@@ -0,0 +1,9 @@
+CREATE TABLE blacklist_event (
+  id serial NOT NULL PRIMARY KEY,
+  eventdata jsonb
+);
+--;;
+CREATE INDEX index_blacklist_event_resource_user ON blacklist_event(
+  (eventdata->>'blacklist/resource'),
+  (eventdata->>'blacklist/user')
+)
diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql
index 75d244b77..1eacf37e5 100644
--- a/resources/sql/queries.sql
+++ b/resources/sql/queries.sql
@@ -521,3 +521,19 @@ VALUES (:prefix, :suffix);
 
 -- :name get-database-time :? :1
 SELECT now();
+
+-- :name add-blacklist-event! :!
+INSERT INTO blacklist_event (eventdata)
+VALUES (:eventdata::jsonb);
+
+-- :name get-blacklist-events :? :*
+SELECT id, eventdata::text FROM blacklist_event
+WHERE 1=1
+/*~ (when (:resource params) */
+  AND eventdata->>'blacklist/resource' = :resource
+/*~ ) ~*/
+/*~ (when (:user params) */
+  AND eventdata->>'blacklist/user' = :user
+/*~ ) ~*/
+ORDER BY id ASC
+;
diff --git a/resources/translations/en.edn b/resources/translations/en.edn
index fad0e0b05..8f91efa3a 100644
--- a/resources/translations/en.edn
+++ b/resources/translations/en.edn
@@ -12,7 +12,7 @@
             :cancel "Cancel"
             :close "Close application"
             :close-intro [:div.intro [:p "Note that if the application is closed, it cannot be opened again."]]
-            :comment "Comment"
+            :comment-placeholder "Comment"
             :decide "Decide"
             :empty "No applications to process."
             :errors {:disabled-catalogue-item "Resource no longer available."
@@ -111,29 +111,28 @@
                  ;;   code to match the term in UI.
                  :description "Title"
                  :empty "No applications."
-                 :events {:approved "%1 approved the application."
-                          :closed "%1 closed the application."
-                          :comment-requested "%1 requested a review from %2."
-                          :commented "%1 reviewed the application."
-                          :copied-from "%1 copied the application from an old application."
-                          :copied-to "%1 copied the application to a new application."
-                          :created "%1 created a new application."
-                          :decision-requested "%1 requested a decision from %2."
-                          :decided "%1 filed a decision for the application."
-                          :draft-saved "%1 saved the application as a draft."
-                          :licenses-accepted "%1 accepted the terms of use."
-                          :licenses-added "%1 added terms of use."
-                          :member-added "%1 added %2 to the application."
-                          :member-invited "%1 invited %2 to the application."
-                          :member-joined "%1 joined to the application."
-                          :member-removed "%1 removed %2 from the application."
-                          :member-uninvited "%1 removed %2 from the application."
-                          :rejected "%1 rejected the application."
-                          :remarked "%1 added a remark."
-                          :resources-changed "%1 changed the resources. New resources: %2."
-                          :returned "%1 requested changes to the application."
-                          :revoked "%1 revoked the application."
-                          :submitted "%1 submitted the application for review."
+                 :events {:approved "Approved"
+                          :closed "Closed"
+                          :comment-requested "Review requested"
+                          :commented "Reviewed"
+                          :copied-from "Copied from an old application"
+                          :copied-to "Copied to a new application"
+                          :created "Created"
+                          :decided "Decided"
+                          :decision-requested "Decision requested"
+                          :draft-saved "Saved"
+                          :licenses-accepted "Accepted terms of use"
+                          :licenses-added "Added terms of use"
+                          :member-added "Member added"
+                          :member-invited "Member invited"
+                          :member-joined "Member joined"
+                          :member-removed "Member removed"
+                          :member-uninvited "Member uninvited"
+                          :rejected "Rejected"
+                          :remarked "Remarked"
+                          :resources-changed "Resources changed"
+                          :returned "Changes requested"
+                          :submitted "Applied"
                           :unknown "Unknown"}
                  :id "Id"
                  :intro [:div.intro [:p "By filling and sending in this application, the applicants may be granted the applied resources. All the applicable terms of use must also be accepted by each applicant."]]
@@ -147,7 +146,6 @@
                           :draft "Draft"
                           :rejected "Rejected"
                           :returned "Returned"
-                          :revoked "Revoked"
                           :submitted "Applied"
                           :unknown "Unknown"}
                  :submitted "Sent"
@@ -240,10 +238,6 @@
                                  :message-to-applicant "Dear %1,\n\nYour application %3 has been returned for your consideration. Please, amend according to requests and resubmit.\n\nYou can review the application at %6"
                                  :subject-to-handler "(%3) Application has been returned to applicant"
                                  :message-to-handler "Dear %1,\n\n%2 has returned the application %3 to the applicant %4 for modifications.\n\nYou can view the application at %6"}
-          :application-revoked {:subject-to-applicant "Your application %3 has been revoked"
-                                :message-to-applicant "Dear %1,\n\nYour application %3 has been revoked.\n\nYou can view the application at %6"
-                                :subject-to-handler "(%3) Application has been revoked"
-                                :message-to-handler "Dear %1,\n\n%2 has revoked the application %3 from %4.\n\nYou can view the application at %6"}
           :application-licenses-added {:subject "Your application's %3 terms of use have changed"
                                        :message "Dear %1,\n\n%2 has requested your approval for changed terms of use to application %3.\n\nYou can review the application and approve the changed the terms of use at %6"}
           :application-resubmitted {:subject "(%3) Application has been resubmitted"
diff --git a/resources/translations/fi.edn b/resources/translations/fi.edn
index be8ef3a1d..e6b7fb5bf 100644
--- a/resources/translations/fi.edn
+++ b/resources/translations/fi.edn
@@ -12,7 +12,7 @@
             :cancel "Peruuta"
             :close "Sulje hakemus"
             :close-intro [:div.intro [:p "Huomaa, että jos hakemus suljetaan, sitä ei voida avata uudelleen."]]
-            :comment "Kommentti"
+            :comment-placeholder "Kommentti"
             :decide "Päätä"
             :empty "Ei käsiteltäviä hakemuksia."
             :errors {:disabled-catalogue-item "Resurssit eivät ole enää saatavilla."
@@ -109,29 +109,28 @@
                  :created "Luotu"
                  :description "Otsikko"
                  :empty "Ei hakemuksia."
-                 :events {:approved "%1 hyväksyi hakemuksen."
-                          :closed "%1 sulki hakemuksen."
-                          :comment-requested "%1 pyysi katselmointia käyttäjältä %2."
-                          :commented "%1 katselmoi hakemuksen."
-                          :copied-from "%1 kopioi hakemuksen vanhasta hakemuksesta."
-                          :copied-to "%1 kopioi hakemuksen uudeksi hakemukseksi."
-                          :created "%1 loi uuden hakemuksen."
-                          :decision-requested "%1 pyysi päätöstä käyttäjältä %2."
-                          :decided "%1 teki päätöksen hakemukselle."
-                          :draft-saved "%1 tallensi hakemuksen luonnoksen."
-                          :licenses-accepted "%1 hyväksyi käyttöehdot."
-                          :licenses-added "%1 lisäsi käyttöehtoja."
-                          :member-added "%1 lisäsi käyttäjän %2 hakemuksen jäseneksi."
-                          :member-invited "%1 kutsui henkilön %2 hakemuksen jäseneksi."
-                          :member-joined "Henkilö %1 liittyi hakemuksen jäseneksi."
-                          :member-removed "%1 poisti hakemuksesta käyttäjän %2."
-                          :member-uninvited "%1 poisti hakemuksesta henkilön %2."
-                          :rejected "%1 hylkäsi hakemuksen."
-                          :remarked "%1 kirjasi huomion."
-                          :resources-changed "%1 muutti haettavia resursseja. Uudet resurssit: %2."
-                          :returned "%1 palautti hakemuksen muokattavaksi."
-                          :revoked "%1 perui hakemuksen."
-                          :submitted "%1 lähetti hakemuksen käsiteltäväksi."
+                 :events {:approved "Hyväksytty"
+                          :closed "Suljettu"
+                          :comment-requested "Katselmointia pyydetty"
+                          :commented "Katselmoitu"
+                          :copied-from "Kopioitu vanhasta hakemuksesta"
+                          :copied-to "Kopioitu uudeksi hakemukseksi"
+                          :created "Luotu"
+                          :decided "Päätetty"
+                          :decision-requested "Päätöstä pyydetty"
+                          :draft-saved "Tallennettu"
+                          :licenses-accepted "Käyttöehdot hyväksytty"
+                          :licenses-added "Käyttöehtoja lisätty"
+                          :member-added "Jäsen lisätty"
+                          :member-invited "Jäsen kutsuttu"
+                          :member-joined "Jäsen liittyi"
+                          :member-removed "Jäsen poistettu"
+                          :member-uninvited "Jäsen poistettu"
+                          :rejected "Hylätty"
+                          :remarked "Huomio kirjattu"
+                          :resources-changed "Resursseja muutettu"
+                          :returned "Palautettu muokattavaksi"
+                          :submitted "Haettu"
                           :unknown "Tuntematon"}
                  :id "Tunniste"
                  :intro [:div.intro [:p "Täyttämällä ja lähettämällä tämän hakemuksen kaikki hakijat voivat saada oikeuden haettuihin resursseihin. Kunkin hakijan on lisäksi hyväksyttävä käyttöehdot viimeistään hakemuksen käsittelyn jälkeen."]]
@@ -145,7 +144,6 @@
                           :draft "Luonnos"
                           :rejected "Hylätty"
                           :returned "Palautettu"
-                          :revoked "Peruttu"
                           :submitted "Haettu"
                           :unknown "Tuntematon"}
                  :submitted "Lähetetty"
@@ -238,10 +236,6 @@
                                  :message-to-applicant "Hyvä %1,\n\nHakemuksesi %3 on palautettu muokattavaksesi. Ole hyvä ja täydennä hakemusta kommenttien mukaan.\n\nVoit tarkastella hakemusta osoitteessa %6"
                                  :subject-to-handler "(%3) Hakemus on palautettu"
                                  :message-to-handler "Hyvä %1,\n\n%2 on palauttanut käyttäjän %4 hakemuksen %3.\n\nVoit tarkastella hakemusta osoitteessa %6"}
-          :application-revoked {:subject-to-applicant "Hakemuksesi %3 on peruttu"
-                                :message-to-applicant "Hyvä %1,\n\nHakemuksesi %3 on peruttu.\n\nVoit tarkastella hakemusta: %6"
-                                :subject-to-handler "(%3) Hakemus on peruttu"
-                                :message-to-handler "Hyvä %1,\n\n%2 on perunut käyttäjän %4 hakemuksen %3.\n\nVoit tarkastella hakemusta: %6"}
           :application-licenses-added {:subject "Hakemuksen %3 käyttöehdot ovat muuttuneet"
                                        :message "Hyvä %1,\n\n%2 pyytää hyväksyntääsi muuttuneille käyttöehdoille hakemukseen %3.\n\nVoit tarkastella hakemusta ja hyväksyä muuttuneet käyttöehdot osoitteessa %6"}
           :application-resubmitted {:subject "(%3) Hakemus lähetetty uudelleen"
diff --git a/src/clj/rems/api.clj b/src/clj/rems/api.clj
index 0042628ba..bc05f7be7 100644
--- a/src/clj/rems/api.clj
+++ b/src/clj/rems/api.clj
@@ -5,6 +5,7 @@
             [compojure.api.sweet :refer :all]
             [conman.core :as conman]
             [rems.api.applications :refer [applications-api my-applications-api]]
+            [rems.api.blacklist :refer [blacklist-api]]
             [rems.api.catalogue :refer [catalogue-api]]
             [rems.api.catalogue-items :refer [catalogue-items-api]]
             [rems.api.entitlements :refer [entitlements-api]]
@@ -107,6 +108,7 @@
 
       my-applications-api
       applications-api
+      blacklist-api
       catalogue-api
       catalogue-items-api
       entitlements-api
diff --git a/src/clj/rems/api/applications.clj b/src/clj/rems/api/applications.clj
index 14564d39b..5d58f539a 100644
--- a/src/clj/rems/api/applications.clj
+++ b/src/clj/rems/api/applications.clj
@@ -11,7 +11,6 @@
             [rems.context :as context]
             [rems.db.applications :as applications]
             [rems.db.attachments :as attachments]
-            [rems.db.core :as db]
             [rems.db.users :as users]
             [rems.text :refer [with-language]]
             [rems.util :refer [getx-user-id update-present]]
@@ -265,7 +264,6 @@
     (command-endpoint :application.command/request-decision commands/RequestDecisionCommand)
     (command-endpoint :application.command/remove-member commands/RemoveMemberCommand)
     (command-endpoint :application.command/return commands/ReturnCommand)
-    (command-endpoint :application.command/revoke commands/RevokeCommand)
     (command-endpoint :application.command/save-draft commands/SaveDraftCommand)
     (command-endpoint :application.command/submit commands/SubmitCommand)
     (command-endpoint :application.command/uninvite-member commands/UninviteMemberCommand)
@@ -279,25 +277,4 @@
                   404 {:schema s/Str :description "Not found"}}
       (if-let [app (applications/get-application (getx-user-id) application-id)]
         (ok app)
-        (api-util/not-found-json-response)))
-
-    (GET "/:application-id/license-attachment/:license-id/:language" []
-      :summary "Get file associated with licence of type attachment associated with application."
-      :roles #{:logged-in}
-      :path-params [application-id :- (describe s/Int "application id")
-                    license-id :- (describe s/Int "license id")
-                    language :- (describe s/Keyword "language code")]
-      (if-let [app (applications/get-application (getx-user-id) application-id)]
-        (if-let [license (some #(when (= license-id (:license/id %)) %)
-                               (:application/licenses app))]
-          (if-let [attachment-id (get-in license [:license/attachment-id language])]
-            (if-let [attachment (db/get-license-attachment {:attachmentId attachment-id})]
-              (do (attachments/check-attachment-content-type (:type attachment))
-                  (-> (:data attachment)
-                      (java.io.ByteArrayInputStream.)
-                      (ok)
-                      (content-type (:type attachment))))
-              (api-util/not-found-json-response))
-            (api-util/not-found-json-response))
-          (api-util/not-found-json-response))
         (api-util/not-found-json-response)))))
diff --git a/src/clj/rems/api/blacklist.clj b/src/clj/rems/api/blacklist.clj
new file mode 100644
index 000000000..86824dc27
--- /dev/null
+++ b/src/clj/rems/api/blacklist.clj
@@ -0,0 +1,55 @@
+(ns rems.api.blacklist
+  (:require [clj-time.core :as time]
+            [compojure.api.sweet :refer :all]
+            [rems.api.schema :as schema]
+            [rems.db.blacklist :as blacklist]
+            [rems.util :refer [getx-user-id]]
+            [ring.util.http-response :refer [ok]]
+            [schema.core :as s]))
+
+(s/defschema BlacklistCommand
+  {:resource blacklist/ResourceId
+   :user blacklist/UserId
+   :comment s/Str})
+
+(s/defschema BlacklistResponse
+  [{:resource blacklist/ResourceId
+    :user blacklist/UserId}])
+
+(defn- command->event [command]
+  {:event/actor (getx-user-id)
+   :event/time (time/now)
+   :blacklist/user (:user command)
+   :blacklist/resource (:resource command)
+   :event/comment (:comment command)})
+
+(def blacklist-api
+  (context "/blacklist" []
+    :tags ["blacklist"]
+    (GET "/" []
+      :summary "Get blacklist entries"
+      :roles #{:handler :owner}
+      :query-params [{user :- blacklist/UserId nil}
+                     {resource :- blacklist/ResourceId nil}]
+      :return BlacklistResponse
+      (ok (blacklist/get-blacklist {:blacklist/user user
+                                    :blacklist/resource resource})))
+    (POST "/add" []
+      :summary "Add a blacklist entry"
+      ;; TODO who can add entries? (see #1682)
+      :roles #{:owner}
+      :body [command BlacklistCommand]
+      :return schema/SuccessResponse
+      (blacklist/add-event! (assoc (command->event command)
+                                   :event/type :blacklist.event/add))
+      (ok {:success true}))
+
+    (POST "/remove" []
+      :summary "Remove a blacklist entry"
+      ;; TODO who can remove entries? (see #1682)
+      :roles #{:owner}
+      :body [command BlacklistCommand]
+      :return schema/SuccessResponse
+      (blacklist/add-event! (assoc (command->event command)
+                                   :event/type :blacklist.event/remove))
+      (ok {:success true}))))
diff --git a/src/clj/rems/api/catalogue_items.clj b/src/clj/rems/api/catalogue_items.clj
index e6f21f618..8cfa82d89 100644
--- a/src/clj/rems/api/catalogue_items.clj
+++ b/src/clj/rems/api/catalogue_items.clj
@@ -5,25 +5,16 @@
             [rems.api.services.catalogue :as catalogue]
             [rems.api.util :refer [not-found-json-response check-user]] ; required for route :roles
             [rems.db.core :as db]
-            [ring.swagger.json-schema :as rjs]
             [ring.util.http-response :refer :all]
             [schema.core :as s]))
 
 (s/defschema GetCatalogueItemsResponse
   [CatalogueItem])
 
-(s/defschema CatalogueItemLocalization
-  {:title s/Str
-   ;; s/optional-key to keep backwards compatibility. s/maybe to allow unsetting the field.
-   (s/optional-key :infourl) (s/maybe s/Str)})
-
 (s/defschema WriteCatalogueItemLocalizations
-  (rjs/field {Language CatalogueItemLocalization}
-             {:description "Localizations keyed by language"
-              :example {:fi {:title "Title in Finnish"
-                             :infourl "http://example.fi"}
-                        :en {:title "Title in English"
-                             :infourl "http://example.com"}}}))
+  {s/Keyword {:title s/Str
+              ;; s/optional-key to keep backwards compatibility. s/maybe to allow unsetting the field.
+              (s/optional-key :infourl) (s/maybe s/Str)}})
 
 ;; TODO resid is misleading: it's the internal id, not the string id
 ;; Should we take the string id instead?
diff --git a/src/clj/rems/api/licenses.clj b/src/clj/rems/api/licenses.clj
index 8f0170c9a..1f2efe4ba 100644
--- a/src/clj/rems/api/licenses.clj
+++ b/src/clj/rems/api/licenses.clj
@@ -7,27 +7,15 @@
             [rems.api.services.licenses :as licenses]
             [rems.util :refer [getx-user-id]]
             [ring.middleware.multipart-params :as multipart]
-            [ring.swagger.json-schema :as rjs]
             [ring.swagger.upload :as upload]
             [ring.util.http-response :refer :all]
             [schema.core :as s]))
 
-(s/defschema LicenseLocalization
-  {:title s/Str
-   :textcontent s/Str
-   (s/optional-key :attachment-id) (rjs/describe (s/maybe s/Int) "For licenses of type attachment")})
-
-(s/defschema LicenseLocalizations
-  (rjs/field {Language LicenseLocalization}
-             {:description "Licence localizations keyed by language"
-              :example {:en {:title "English title"
-                             :textcontent "English content"}
-                        :fi {:title "Finnish title"
-                             :textcontent "Finnish content"}}}))
-
 (s/defschema CreateLicenseCommand
   {:licensetype (s/enum "link" "text" "attachment")
-   :localizations LicenseLocalizations})
+   :localizations {s/Keyword {:title s/Str
+                              :textcontent s/Str
+                              (s/optional-key :attachment-id) (s/maybe s/Int)}}})
 
 (s/defschema AttachmentMetadata
   {:id s/Int})
diff --git a/src/clj/rems/api/schema.clj b/src/clj/rems/api/schema.clj
index 564fa3433..a9b78c97f 100644
--- a/src/clj/rems/api/schema.clj
+++ b/src/clj/rems/api/schema.clj
@@ -1,7 +1,6 @@
 (ns rems.api.schema
   "Shared schema definitions for the API"
   (:require [rems.application.events :as events]
-            [ring.swagger.json-schema :as rjs]
             [schema.core :as s])
   (:import (org.joda.time DateTime)))
 
@@ -68,22 +67,11 @@
   {:success s/Bool
    (s/optional-key :errors) [s/Any]})
 
-(s/defschema Language
-  (rjs/field s/Keyword
-             {:description "A language code"
-              :example "en"}))
-
 (s/defschema LocalizedString
-  (rjs/field {Language s/Str}
-             {:example {:fi "text in Finnish"
-                        :en "text in English"}
-              :description "Text values keyed by languages"}))
+  {s/Keyword s/Str})
 
 (s/defschema LocalizedInt
-  (rjs/field {Language s/Int}
-             {:example {:fi 1
-                        :en 2}
-              :description "Integers keyed by languages"}))
+  {s/Keyword s/Int})
 
 (s/defschema V2Resource
   {:resource/id s/Int
diff --git a/src/clj/rems/application/commands.clj b/src/clj/rems/application/commands.clj
index eb28980f2..3d7cf09bf 100644
--- a/src/clj/rems/application/commands.clj
+++ b/src/clj/rems/application/commands.clj
@@ -24,83 +24,100 @@
 (s/defschema CommandBase
   {:application-id s/Int})
 
-(s/defschema AcceptInvitationCommand
+(s/defschema CreateCommand
+  {:catalogue-item-ids [s/Int]})
+
+(s/defschema SaveDraftCommand
   (assoc CommandBase
-         :token s/Str))
+         ;; {s/Int s/Str} is what we want, but that isn't nicely representable as JSON
+         :field-values [{:field s/Int
+                         :value s/Str}]))
+
 (s/defschema AcceptLicensesCommand
   (assoc CommandBase
          :accepted-licenses [s/Int]))
-(s/defschema AddLicensesCommand
-  (assoc CommandBase
-         :comment s/Str
-         :licenses [s/Int]))
-(s/defschema AddMemberCommand
-  (assoc CommandBase
-         :member {:userid UserId}))
+
+(s/defschema SubmitCommand
+  CommandBase)
+
 (s/defschema ApproveCommand
   (assoc CommandBase
          :comment s/Str))
-(s/defschema ChangeResourcesCommand
+
+(s/defschema RejectCommand
   (assoc CommandBase
-         (s/optional-key :comment) s/Str
-         :catalogue-item-ids [s/Int]))
+         :comment s/Str))
+
+(s/defschema ReturnCommand
+  (assoc CommandBase
+         :comment s/Str))
+
 (s/defschema CloseCommand
   (assoc CommandBase
          :comment s/Str))
-(s/defschema CommentCommand
+
+(s/defschema RequestDecisionCommand
   (assoc CommandBase
+         :deciders [UserId]
          :comment s/Str))
-(s/defschema CopyAsNewCommand
-  CommandBase)
-(s/defschema CreateCommand
-  {:catalogue-item-ids [s/Int]})
+
 (s/defschema DecideCommand
   (assoc CommandBase
          :decision (s/enum :approved :rejected)
          :comment s/Str))
-(s/defschema InviteMemberCommand
+
+;; TODO RequestComment/Comment could be renamed to RequestReview/Review to be in line with the UI
+(s/defschema RequestCommentCommand
   (assoc CommandBase
-         :member {:name s/Str
-                  :email s/Str}))
-(s/defschema RejectCommand
+         :commenters [UserId]
+         :comment s/Str))
+
+(s/defschema CommentCommand
   (assoc CommandBase
          :comment s/Str))
+
 (s/defschema RemarkCommand
   (assoc CommandBase
          :comment s/Str
          :public s/Bool))
-(s/defschema RemoveMemberCommand
+
+(s/defschema AddLicensesCommand
   (assoc CommandBase
-         :member {:userid UserId}
-         :comment s/Str))
-;; TODO RequestComment/Comment could be renamed to RequestReview/Review to be in line with the UI
-(s/defschema RequestCommentCommand
+         :comment s/Str
+         :licenses [s/Int]))
+
+(s/defschema AddMemberCommand
   (assoc CommandBase
-         :commenters [UserId]
-         :comment s/Str))
-(s/defschema RequestDecisionCommand
+         :member {:userid UserId}))
+
+(s/defschema ChangeResourcesCommand
   (assoc CommandBase
-         :deciders [UserId]
-         :comment s/Str))
-(s/defschema ReturnCommand
+         (s/optional-key :comment) s/Str
+         :catalogue-item-ids [s/Int]))
+
+(s/defschema InviteMemberCommand
   (assoc CommandBase
-         :comment s/Str))
-(s/defschema RevokeCommand
+         :member {:name s/Str
+                  :email s/Str}))
+
+(s/defschema AcceptInvitationCommand
   (assoc CommandBase
-         :comment s/Str))
-(s/defschema SaveDraftCommand
+         :token s/Str))
+
+(s/defschema RemoveMemberCommand
   (assoc CommandBase
-         ;; {s/Int s/Str} is what we want, but that isn't nicely representable as JSON
-         :field-values [{:field s/Int
-                         :value s/Str}]))
-(s/defschema SubmitCommand
-  CommandBase)
+         :member {:userid UserId}
+         :comment s/Str))
+
 (s/defschema UninviteMemberCommand
   (assoc CommandBase
          :member {:name s/Str
                   :email s/Str}
          :comment s/Str))
 
+(s/defschema CopyAsNewCommand
+  CommandBase)
+
 (def command-schemas
   {:application.command/accept-invitation AcceptInvitationCommand
    :application.command/accept-licenses AcceptLicensesCommand
@@ -120,7 +137,6 @@
    :application.command/request-comment RequestCommentCommand
    :application.command/request-decision RequestDecisionCommand
    :application.command/return ReturnCommand
-   :application.command/revoke RevokeCommand
    :application.command/save-draft SaveDraftCommand
    :application.command/submit SubmitCommand
    :application.command/uninvite-member UninviteMemberCommand})
@@ -355,11 +371,6 @@
   (ok {:event/type :application.event/closed
        :application/comment (:comment cmd)}))
 
-(defmethod command-handler :application.command/revoke
-  [cmd _application _injections]
-  (ok {:event/type :application.event/revoked
-       :application/comment (:comment cmd)}))
-
 (defmethod command-handler :application.command/request-decision
   [cmd _application injections]
   (or (must-not-be-empty cmd :deciders)
diff --git a/src/clj/rems/application/events.clj b/src/clj/rems/application/events.clj
index b4515ac15..42e874eb5 100644
--- a/src/clj/rems/application/events.clj
+++ b/src/clj/rems/application/events.clj
@@ -38,16 +38,6 @@
          :application/request-id s/Uuid
          :application/commenters [s/Str]
          :application/comment s/Str))
-(s/defschema CopiedFromEvent
-  (assoc EventBase
-         :event/type (s/enum :application.event/copied-from)
-         :application/copied-from {:application/id s/Int
-                                   :application/external-id s/Str}))
-(s/defschema CopiedToEvent
-  (assoc EventBase
-         :event/type (s/enum :application.event/copied-to)
-         :application/copied-to {:application/id s/Int
-                                 :application/external-id s/Str}))
 (s/defschema CreatedEvent
   (assoc EventBase
          :event/type (s/enum :application.event/created)
@@ -128,21 +118,25 @@
   (assoc EventBase
          :event/type (s/enum :application.event/returned)
          :application/comment s/Str))
-(s/defschema RevokedEvent
-  (assoc EventBase
-         :event/type (s/enum :application.event/revoked)
-         :application/comment s/Str))
 (s/defschema SubmittedEvent
   (assoc EventBase
          :event/type (s/enum :application.event/submitted)))
+(s/defschema CopiedFromEvent
+  (assoc EventBase
+         :event/type (s/enum :application.event/copied-from)
+         :application/copied-from {:application/id s/Int
+                                   :application/external-id s/Str}))
+(s/defschema CopiedToEvent
+  (assoc EventBase
+         :event/type (s/enum :application.event/copied-to)
+         :application/copied-to {:application/id s/Int
+                                 :application/external-id s/Str}))
 
 (def event-schemas
   {:application.event/approved ApprovedEvent
    :application.event/closed ClosedEvent
-   :application.event/comment-requested CommentRequestedEvent
    :application.event/commented CommentedEvent
-   :application.event/copied-from CopiedFromEvent
-   :application.event/copied-to CopiedToEvent
+   :application.event/comment-requested CommentRequestedEvent
    :application.event/created CreatedEvent
    :application.event/decided DecidedEvent
    :application.event/decision-requested DecisionRequestedEvent
@@ -158,8 +152,9 @@
    :application.event/remarked RemarkedEvent
    :application.event/resources-changed ResourcesChangedEvent
    :application.event/returned ReturnedEvent
-   :application.event/revoked RevokedEvent
-   :application.event/submitted SubmittedEvent})
+   :application.event/submitted SubmittedEvent
+   :application.event/copied-from CopiedFromEvent
+   :application.event/copied-to CopiedToEvent})
 
 (s/defschema Event
   (apply r/dispatch-on :event/type (flatten (seq event-schemas))))
diff --git a/src/clj/rems/application/model.clj b/src/clj/rems/application/model.clj
index 98c02ac1d..603839066 100644
--- a/src/clj/rems/application/model.clj
+++ b/src/clj/rems/application/model.clj
@@ -46,8 +46,7 @@
     :application.command/request-decision
     :application.command/return
     :application.command/approve
-    :application.command/reject
-    :application.command/close})
+    :application.command/reject})
 
 (def ^:private handler-returned-commands
   (disj handler-all-commands
@@ -92,8 +91,7 @@
               :application.command/remove-member
               :application.command/invite-member
               :application.command/uninvite-member
-              :application.command/close
-              :application.command/revoke}})
+              :application.command/close}})
 
 (def ^:private closed-permissions
   {:applicant #{:application.command/copy-as-new}
@@ -180,7 +178,6 @@
     :application.state/draft
     :application.state/rejected
     :application.state/returned
-    :application.state/revoked
     :application.state/submitted})
 
 (defmulti ^:private event-type-specific-application-view
@@ -348,12 +345,6 @@
       (assoc :application/state :application.state/closed)
       (assoc :application/todo nil)))
 
-(defmethod event-type-specific-application-view :application.event/revoked
-  [application _event]
-  (-> application
-      (assoc :application/state :application.state/revoked)
-      (assoc :application/todo nil)))
-
 (defmethod event-type-specific-application-view :application.event/copied-from
   [application event]
   (-> application
@@ -476,8 +467,8 @@
                          :field/value)]
     (assoc application :application/description (str description))))
 
-(defn- enrich-resources [resources get-catalogue-item]
-  (->> resources
+(defn- enrich-resources [app-resources get-catalogue-item]
+  (->> app-resources
        (map (fn [resource]
               (let [item (get-catalogue-item (:catalogue-item/id resource))]
                 {:catalogue-item/id (:catalogue-item/id resource)
@@ -515,28 +506,6 @@
                            (sort-by :license/id))]
     (merge-lists-by :license/id rich-licenses app-licenses)))
 
-(defn- enrich-events [events get-user get-catalogue-item]
-  (->> events
-       (map (fn [event]
-              (let [event-type (:event/type event)]
-                (merge event
-                       {:event/actor-attributes (get-user (:event/actor event))}
-                       (case event-type
-                         :application.event/resources-changed
-                         {:application/resources (enrich-resources (:application/resources event) get-catalogue-item)}
-
-                         :application.event/decision-requested
-                         {:application/deciders (mapv get-user (:application/deciders event))}
-
-                         :application.event/comment-requested
-                         {:application/commenters (mapv get-user (:application/commenters event))}
-
-                         (:application.event/member-added
-                          :application.event/member-removed)
-                         {:application/member (get-user (:userid (:application/member event)))}
-
-                         {})))))))
-
 (defn- enrich-user-attributes [application get-user]
   (letfn [(enrich-members [members]
             (->> members
@@ -584,7 +553,6 @@
         set-application-description
         (update :application/resources enrich-resources get-catalogue-item)
         (update :application/licenses enrich-licenses get-license)
-        (update :application/events enrich-events get-user get-catalogue-item)
         (assoc :application/applicant-attributes (get-user (:application/applicant application)))
         (assoc :application/attachments (get-attachments-for-application (getx application :application/id)))
         (enrich-user-attributes get-user)
diff --git a/src/clj/rems/db/blacklist.clj b/src/clj/rems/db/blacklist.clj
new file mode 100644
index 000000000..aa29c9493
--- /dev/null
+++ b/src/clj/rems/db/blacklist.clj
@@ -0,0 +1,66 @@
+(ns rems.db.blacklist
+  (:require [clj-time.format :as time-format]
+            [rems.db.core :as db]
+            [rems.json :as json]
+            [schema.coerce :as coerce]
+            [schema.core :as s]
+            [schema.utils])
+  (:import (org.joda.time DateTime)))
+
+;; TODO copied from rems.application.events:
+(def UserId s/Str)
+
+(def ResourceId s/Str)
+
+(def BlacklistEvent
+  {(s/optional-key :event/id) s/Int
+   :event/type (s/enum :blacklist.event/add :blacklist.event/remove)
+   :event/time DateTime
+   :event/actor UserId
+   :blacklist/user UserId
+   :blacklist/resource ResourceId ;; resource/ext-id
+   :event/comment (s/maybe s/Str)})
+
+(def ^:private coerce-event
+  (coerce/coercer BlacklistEvent json/coercion-matcher))
+
+(defn- json->event [json]
+  (let [result (-> json
+                   json/parse-string
+                   coerce-event)]
+    (when (schema.utils/error? result)
+      (throw (ex-info (str "Value does not match schema: " (pr-str result))
+                      {:value json :error result})))
+    result))
+
+(defn- event->json [event]
+  (s/validate BlacklistEvent event)
+  (json/generate-string event))
+
+(defn add-event! [event]
+  (db/add-blacklist-event! {:eventdata (event->json event)}))
+
+(defn- event-from-db [event]
+  (assoc (json->event (:eventdata event))
+         :event/id (:id event)))
+
+(defn get-events [params]
+  (mapv event-from-db (db/get-blacklist-events {:user (:blacklist/user params)
+                                                :resource (:blacklist/resource params)})))
+
+(defn- events->blacklist [events]
+  ;; TODO: move computation to db for performance
+  ;; should be enough to check latest event per user-resource pair
+  (reduce (fn [blacklist event]
+            (let [entry {:resource (:blacklist/resource event)
+                         :user (:blacklist/user event)}]
+              (case (:event/type event)
+                :blacklist.event/add
+                (conj blacklist entry)
+                :blacklist.event/remove
+                (disj blacklist entry))))
+          #{}
+          events))
+
+(defn get-blacklist [params]
+  (vec (sort-by (juxt :resource :user) (events->blacklist (get-events params)))))
diff --git a/src/clj/rems/db/entitlements.clj b/src/clj/rems/db/entitlements.clj
index 098d4465d..650e16977 100644
--- a/src/clj/rems/db/entitlements.clj
+++ b/src/clj/rems/db/entitlements.clj
@@ -94,39 +94,41 @@
   and call the entitlement REST callback (if defined). Likewise if a resource is removed, member left etc.
   then we end the entitlement and call the REST callback."
   [application]
-  (let [application-id (:application/id application)
-        current-members (set (concat (map :userid (:application/members application))
-                                     [(:application/applicant application)]))
-        past-members (set (map :userid (:application/past-members application)))
-        application-state (:application/state application)
-        application-resources (->> application
-                                   :application/resources
-                                   (map :resource/id)
-                                   set)
-        application-entitlements (get-entitlements-by-user application-id)
-        is-entitled? (fn [userid resource-id]
-                       (and (= :application.state/approved application-state)
-                            (contains? current-members userid)
-                            (accepted-licenses? application userid)
-                            (contains? application-resources resource-id)))
-        entitlements-by-user (fn [userid] (or (application-entitlements userid) #{}))
-        entitlements-to-add (->> (for [userid (union current-members past-members)
-                                       :let [current-resource-ids (entitlements-by-user userid)]
-                                       resource-id application-resources
-                                       :when (is-entitled? userid resource-id)
-                                       :when (not (contains? current-resource-ids resource-id))]
-                                   {userid #{resource-id}})
-                                 (apply merge-with union))
-        entitlements-to-remove (->> (for [userid (union current-members past-members)
-                                          :let [resource-ids (entitlements-by-user userid)]
-                                          resource-id resource-ids
-                                          :when (not (is-entitled? userid resource-id))]
-                                      {userid #{resource-id}})
-                                    (apply merge-with union))
-        members-to-update (keys (merge entitlements-to-add entitlements-to-remove))]
-    (when (seq members-to-update)
-      (log/info "updating entitlements on application" application-id)
-      (doseq [[userid resource-ids] entitlements-to-add]
-        (grant-entitlements! application-id userid resource-ids))
-      (doseq [[userid resource-ids] entitlements-to-remove]
-        (revoke-entitlements! application-id userid resource-ids)))))
+  (when (contains? #{:application.state/approved :application.state/closed}
+                   (:application/state application))
+    (let [application-id (:application/id application)
+          current-members (set (concat (map :userid (:application/members application))
+                                       [(:application/applicant application)]))
+          past-members (set (map :userid (:application/past-members application)))
+          application-state (:application/state application)
+          application-resources (->> application
+                                     :application/resources
+                                     (map :resource/id)
+                                     set)
+          application-entitlements (get-entitlements-by-user application-id)
+          is-entitled? (fn [userid resource-id]
+                         (and (= :application.state/approved application-state)
+                              (contains? current-members userid)
+                              (accepted-licenses? application userid)
+                              (contains? application-resources resource-id)))
+          entitlements-by-user (fn [userid] (or (application-entitlements userid) #{}))
+          entitlements-to-add (->> (for [userid (union current-members past-members)
+                                         :let [current-resource-ids (entitlements-by-user userid)]
+                                         resource-id application-resources
+                                         :when (is-entitled? userid resource-id)
+                                         :when (not (contains? current-resource-ids resource-id))]
+                                     {userid #{resource-id}})
+                                   (apply merge-with union))
+          entitlements-to-remove (->> (for [userid (union current-members past-members)
+                                            :let [resource-ids (entitlements-by-user userid)]
+                                            resource-id resource-ids
+                                            :when (not (is-entitled? userid resource-id))]
+                                        {userid #{resource-id}})
+                                      (apply merge-with union))
+          members-to-update (keys (merge entitlements-to-add entitlements-to-remove))]
+      (when (seq members-to-update)
+        (log/info "updating entitlements on application" application-id)
+        (doseq [[userid resource-ids] entitlements-to-add]
+          (grant-entitlements! application-id userid resource-ids))
+        (doseq [[userid resource-ids] entitlements-to-remove]
+          (revoke-entitlements! application-id userid resource-ids))))))
diff --git a/src/clj/rems/db/events.clj b/src/clj/rems/db/events.clj
index 7f441865e..15812d220 100644
--- a/src/clj/rems/db/events.clj
+++ b/src/clj/rems/db/events.clj
@@ -8,23 +8,11 @@
             [schema-tools.core :as st])
   (:import [org.joda.time DateTime]))
 
-(defn- datestring->datetime [s]
-  (if (string? s)
-    (time-format/parse s)
-    s))
-
-(def ^:private datestring-coercion-matcher
-  {DateTime datestring->datetime})
-
-(defn- coercion-matcher [schema]
-  (or (datestring-coercion-matcher schema)
-      (coerce/string-coercion-matcher schema)))
-
 (def ^:private coerce-event-commons
-  (coerce/coercer (st/open-schema events/EventBase) coercion-matcher))
+  (coerce/coercer (st/open-schema events/EventBase) json/coercion-matcher))
 
 (def ^:private coerce-event-specifics
-  (coerce/coercer events/Event coercion-matcher))
+  (coerce/coercer events/Event json/coercion-matcher))
 
 (defn- coerce-event [event]
   ;; must coerce the common fields first, so that dynamic/Event can choose the right event schema based on the event type
diff --git a/src/clj/rems/json.clj b/src/clj/rems/json.clj
index 3cd969562..0840857b1 100644
--- a/src/clj/rems/json.clj
+++ b/src/clj/rems/json.clj
@@ -1,9 +1,13 @@
 (ns rems.json
-  (:require [clojure.test :refer [deftest is testing]]
+  (:require [clj-time.core :as time]
+            [clj-time.format :as time-format]
+            [clojure.test :refer [deftest is testing]]
             [cognitect.transit :as transit]
             [cuerdas.core :refer [numeric? parse-number]]
             [jsonista.core :as j]
-            [muuntaja.core :as muuntaja])
+            [muuntaja.core :as muuntaja]
+            [schema.coerce :as coerce]
+            [schema.core :as s])
   (:import [com.fasterxml.jackson.datatype.joda JodaModule]
            [org.joda.time DateTime ReadableInstant DateTimeZone]
            [org.joda.time.format ISODateTimeFormat]))
@@ -63,3 +67,24 @@
                (muuntaja/decode muuntaja format "[\"^ \",\"~:date-time\",\"~t2000-01-01T12:00:00.000Z\"]")))
         (is (= {:date-time (.toDate (DateTime. 2000 1 1 12 0 DateTimeZone/UTC))}
                (muuntaja/decode muuntaja format "[\"^ \",\"~:date-time\",\"~m946728000000\"]")))))))
+
+;;; Utils for schema-based coercion
+
+(defn- datestring->datetime [s]
+  (if (string? s)
+    (time-format/parse s)
+    s))
+
+(def datestring-coercion-matcher
+  {DateTime datestring->datetime})
+
+(defn coercion-matcher [schema]
+  (or (datestring-coercion-matcher schema)
+      (coerce/string-coercion-matcher schema)))
+
+(deftest test-coercion-matcher
+  (let [coercer (coerce/coercer {:time DateTime :type s/Keyword} coercion-matcher)]
+    (is (= {:type :foo
+            :time (time/date-time 2019 03 04 10)}
+           (coercer {:type "foo"
+                     :time "2019-03-04T10:00:00.000Z"})))))
diff --git a/src/clj/rems/poller/email.clj b/src/clj/rems/poller/email.clj
index d85c8897e..cd782e0c9 100644
--- a/src/clj/rems/poller/email.clj
+++ b/src/clj/rems/poller/email.clj
@@ -112,16 +112,6 @@
                                 :t.email.application-rejected/subject-to-handler
                                 :t.email.application-rejected/message-to-handler)))
 
-(defmethod event-to-emails-impl :application.event/revoked [event application]
-  (concat (emails-to-recipients (applicant-and-members application)
-                                event application
-                                :t.email.application-revoked/subject-to-applicant
-                                :t.email.application-revoked/message-to-applicant)
-          (emails-to-recipients (other-handlers event application)
-                                event application
-                                :t.email.application-revoked/subject-to-handler
-                                :t.email.application-revoked/message-to-handler)))
-
 (defmethod event-to-emails-impl :application.event/closed [event application]
   (concat (emails-to-recipients (applicant-and-members application)
                                 event application
diff --git a/src/clj/rems/poller/entitlements.clj b/src/clj/rems/poller/entitlements.clj
index 7a65cd09a..fea8cd558 100644
--- a/src/clj/rems/poller/entitlements.clj
+++ b/src/clj/rems/poller/entitlements.clj
@@ -10,13 +10,13 @@
   (:import [org.joda.time Duration]))
 
 (defn- entitlements-for-event [event]
-  ;; performance improvement: filter events which may affect entitlements
+  ;; we filter by event here, and by state in update-entitlements-for.
+  ;; this is for performance reasons only
   (when (contains? #{:application.event/approved
-                     :application.event/closed
                      :application.event/licenses-accepted
                      :application.event/member-removed
                      :application.event/resources-changed
-                     :application.event/revoked}
+                     :application.event/closed}
                    (:event/type event))
     (let [application (applications/get-unrestricted-application (:application/id event))]
       (entitlements/update-entitlements-for application))))
diff --git a/src/cljc/rems/text.cljc b/src/cljc/rems/text.cljc
index 3669346cf..2e14e6cf9 100644
--- a/src/cljc/rems/text.cljc
+++ b/src/cljc/rems/text.cljc
@@ -1,16 +1,12 @@
 (ns rems.text
   #?(:clj (:require [clj-time.core :as time]
                     [clj-time.format :as format]
-                    [clojure.string :as str]
-                    [rems.application-util :as application-util]
                     [rems.context :as context]
                     [rems.locales :as locales]
                     [taoensso.tempura :refer [tr]])
      :cljs (:require [cljs-time.core :as time]
                      [cljs-time.format :as format]
-                     [clojure.string :as str]
                      [re-frame.core :as rf]
-                     [rems.application-util :as application-util]
                      [taoensso.tempura :refer [tr]])))
 
 (defn with-language [lang f]
@@ -69,8 +65,7 @@
    :application.state/approved :t.applications.states/approved
    :application.state/rejected :t.applications.states/rejected
    :application.state/closed :t.applications.states/closed
-   :application.state/returned :t.applications.states/returned
-   :application.state/revoked :t.applications.states/revoked})
+   :application.state/returned :t.applications.states/returned})
 
 (defn localize-state [state]
   (text (get states state :t.applications.states/unknown)))
@@ -111,43 +106,16 @@
    :application.event/remarked :t.applications.events/remarked
    :application.event/resources-changed :t.applications.events/resources-changed
    :application.event/returned :t.applications.events/returned
-   :application.event/revoked :t.applications.events/revoked
    :application.event/submitted :t.applications.events/submitted})
 
-(defn localize-decision [event]
-  (let [decision (:application/decision event)]
-    (text-format
-     (case decision
-       :approved :t.applications.events/approved
-       :rejected :t.applications.events/rejected
-       :t.applications.events/unknown)
-     (application-util/get-member-name (:event/actor-attributes event)))))
+(defn localize-event [event-type]
+  (text (get event-types event-type :t.applications.events/unknown)))
 
-(defn localize-event [event]
-  (let [event-type (:event/type event)]
-    (text-format
-     (get event-types event-type :t.applications.events/unknown)
-     (application-util/get-member-name (:event/actor-attributes event))
-     (case event-type
-       :application.event/comment-requested
-       (str/join ", " (mapv application-util/get-member-name
-                            (:application/commenters event)))
-
-       :application.event/decision-requested
-       (str/join ", " (mapv application-util/get-member-name
-                            (:application/deciders event)))
-
-       (:application.event/member-added
-        :application.event/member-invited
-        :application.event/member-removed
-        :application.event/member-uninvited)
-       (application-util/get-member-name (:application/member event))
-
-       :application.event/resources-changed
-       (str/join ", " (mapv #(localized (:catalogue-item/title %))
-                            (:application/resources event)))
-
-       nil))))
+(defn localize-decision [decision]
+  (text (case decision
+          :approved :t.applications.events/approved
+          :rejected :t.applications.events/rejected
+          :t.applications.events/unknown)))
 
 (def ^:private time-format
   (format/formatter "yyyy-MM-dd HH:mm" (time/default-time-zone)))
diff --git a/src/cljs/rems/actions/action.cljs b/src/cljs/rems/actions/action.cljs
index d87b9ed9f..8cf323590 100644
--- a/src/cljs/rems/actions/action.cljs
+++ b/src/cljs/rems/actions/action.cljs
@@ -35,7 +35,7 @@
                 :min-rows 4
                 :max-rows 4
                 :name id
-                :placeholder (text :t.actions/comment)
+                :placeholder (text :t.actions/comment-placeholder)
                 :value comment
                 :on-change #(on-comment (.. % -target -value))}]]))
 
diff --git a/src/cljs/rems/administration/form.cljs b/src/cljs/rems/administration/form.cljs
index 4f0ea1818..8e24ca786 100644
--- a/src/cljs/rems/administration/form.cljs
+++ b/src/cljs/rems/administration/form.cljs
@@ -9,7 +9,7 @@
             [rems.common-util :refer [andstr]]
             [rems.flash-message :as flash-message]
             [rems.spinner :as spinner]
-            [rems.text :refer [text]]
+            [rems.text :refer [localize-time text]]
             [rems.util :refer [navigate! fetch]]))
 
 (rf/reg-event-fx
@@ -74,6 +74,8 @@
      :always [:div
               [inline-info-field (text :t.administration/organization) (:form/organization form)]
               [inline-info-field (text :t.administration/title) (:form/title form)]
+              [inline-info-field (text :t.administration/start) (localize-time (:start form))]
+              [inline-info-field (text :t.administration/end) (localize-time (:end form))]
               [inline-info-field (text :t.administration/active) [readonly-checkbox (status-flags/active? form)]]]}]
    (let [id (:form/id form)]
      [:div.col.commands
diff --git a/src/cljs/rems/administration/resource.cljs b/src/cljs/rems/administration/resource.cljs
index 57de23677..05208a850 100644
--- a/src/cljs/rems/administration/resource.cljs
+++ b/src/cljs/rems/administration/resource.cljs
@@ -9,7 +9,7 @@
             [rems.common-util :refer [andstr]]
             [rems.flash-message :as flash-message]
             [rems.spinner :as spinner]
-            [rems.text :refer [text]]
+            [rems.text :refer [localize-time text]]
             [rems.util :refer [fetch]]))
 
 (rf/reg-event-fx
@@ -48,6 +48,8 @@
      :always [:div
               [inline-info-field (text :t.administration/organization) (:organization resource)]
               [inline-info-field (text :t.administration/resource) (:resid resource)]
+              [inline-info-field (text :t.administration/start) (localize-time (:start resource))]
+              [inline-info-field (text :t.administration/end) (localize-time (:end resource))]
               [inline-info-field (text :t.administration/active) [readonly-checkbox (status-flags/active? resource)]]]}]
    [licenses-view (:licenses resource) language]
    (let [id (:id resource)]
diff --git a/src/cljs/rems/administration/workflow.cljs b/src/cljs/rems/administration/workflow.cljs
index e53acf0da..1e80125f7 100644
--- a/src/cljs/rems/administration/workflow.cljs
+++ b/src/cljs/rems/administration/workflow.cljs
@@ -10,7 +10,7 @@
             [rems.common-util :refer [andstr]]
             [rems.flash-message :as flash-message]
             [rems.spinner :as spinner]
-            [rems.text :refer [get-localized-title text text-format]]
+            [rems.text :refer [get-localized-title localize-time text text-format]]
             [rems.util :refer [navigate! fetch]]))
 
 (rf/reg-event-fx
@@ -63,6 +63,8 @@
                                                                          (map enrich-user)
                                                                          (map :display)
                                                                          (str/join ", "))]
+              [inline-info-field (text :t.administration/start) (localize-time (:start workflow))]
+              [inline-info-field (text :t.administration/end) (localize-time (:end workflow))]
               [inline-info-field (text :t.administration/active) [readonly-checkbox (status-flags/active? workflow)]]]}]
    [licenses-view (:licenses workflow) language]
    (let [id (:id workflow)]
diff --git a/src/cljs/rems/application.cljs b/src/cljs/rems/application.cljs
index 37510b15e..e7d91e765 100644
--- a/src/cljs/rems/application.cljs
+++ b/src/cljs/rems/application.cljs
@@ -272,16 +272,13 @@
                      :tab-index "-1"}
       [:div.license-block (str/trim (str text))]]]))
 
-(defn- attachment-license [application license]
+(defn- attachment-license [license]
   (let [title (localized (:license/title license))
-        language @(rf/subscribe [:language])
-        link (str "/api/applications/" (:application/id application)
-                  "/license-attachment/" (:license/id license)
-                  "/" (name language))]
+        link (str "/api/licenses/attachments/" (localized (:license/attachment-id license)))]
     [:a.license-title {:href link :target :_blank}
      title " " [file-download]]))
 
-(defn license-field [application license show-accepted-licenses?]
+(defn license-field [license show-accepted-licenses?]
   [:div.license.flex-row.d-flex
    [:div (when show-accepted-licenses?
            (if (:accepted license)
@@ -290,7 +287,7 @@
    (case (:license/type license)
      :link [link-license license]
      :text [text-license license]
-     :attachment [attachment-license application license]
+     :attachment [attachment-license license]
      [fields/unsupported-field license])])
 
 (defn- save-button []
@@ -362,7 +359,6 @@
          (into [:div#licenses]
                (for [license licenses]
                  [license-field
-                  application
                   (assoc license
                          :accepted (contains? accepted-licenses (:license/id license))
                          :readonly readonly?)
@@ -386,32 +382,32 @@
      (application-list/format-application-id config application)]))
 
 (defn- format-event [event]
-  {:user (:commonName (:event/actor-attributes event))
-   :event (localize-event event)
-   :decision (when (= (:event/type event) :application.event/decided)
-               (localize-decision event))
+  {:userid (:event/actor event)
+   :event (localize-event (:event/type event))
    :comment (case (:event/type event)
+              :application.event/decided
+              (str (localize-decision (:application/decision event)) ": " (:application/comment event))
+
               :application.event/copied-from
               [application-link (:application/copied-from event) (text :t.applications/application)]
 
               :application.event/copied-to
               [application-link (:application/copied-to event) (text :t.applications/application)]
 
-              (let [comment (:application/comment event)]
-                (when (not (empty? comment))
-                  (str (text :t.actions/comment) ": " (:application/comment event)))))
+              (:application/comment event))
    :request-id (:application/request-id event)
+   :commenters (:application/commenters event)
+   :deciders (:application/deciders event)
    :time (localize-time (:event/time event))})
 
-(defn- event-view [{:keys [time user event comment decision]}]
+(defn- event-view [{:keys [time userid event comment commenters deciders]}]
   [:div.row
    [:label.col-sm-2.col-form-label time]
    [:div.col-sm-10
-    [:div.col-form-label event]
-    (when decision
-      [:div decision])
-    (when comment
-      [:div comment])]])
+    [:div.col-form-label [:span userid] " — " [:span event]
+     (when-let [targets (seq (concat commenters deciders))]
+       [:span ": " (str/join ", " targets)])]
+    (when comment [:div comment])]])
 
 (defn- render-event-groups [event-groups]
   (for [group event-groups]


Test patch:
diff --git a/test-data/test-fi.txt b/test-data/test-fi.txt
deleted file mode 100644
index 1d63a4ae5..000000000
--- a/test-data/test-fi.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-tervehdys tiedostosta
diff --git a/test/clj/rems/api/test_applications.clj b/test/clj/rems/api/test_applications.clj
index f98373847..d10e41648 100644
--- a/test/clj/rems/api/test_applications.clj
+++ b/test/clj/rems/api/test_applications.clj
@@ -185,7 +185,6 @@
                  "application.command/invite-member"
                  "application.command/uninvite-member"
                  "application.command/change-resources"
-                 "application.command/close"
                  "see-everything"}
                (set (get application :application/permissions))))))
 
@@ -571,95 +570,6 @@
                          handler)]
         (is (response-is-forbidden? response))))))
 
-(deftest test-application-api-license-attachments
-  (let [api-key "42"
-        applicant "alice"
-        non-applicant "bob"
-        owner "owner"
-        handler-user "developer"
-        cat-id (test-data/create-catalogue-item! {})
-        app-id (test-data/create-application! {:catalogue-item-ids [cat-id]
-                                               :actor applicant})
-        file-en (clojure.java.io/file "./test-data/test.txt")
-        filecontent-en {:tempfile file-en
-                        :content-type "text/plain"
-                        :filename "test.txt"
-                        :size (.length file-en)}
-        en-attachment-id (-> (request :post "/api/licenses/add_attachment")
-                             (assoc :params {"file" filecontent-en})
-                             (assoc :multipart-params {"file" filecontent-en})
-                             (authenticate api-key owner)
-                             handler
-                             read-ok-body
-                             :id)
-
-        file-fi (clojure.java.io/file "./test-data/test-fi.txt")
-        filecontent-fi {:tempfile file-fi
-                        :content-type "text/plain"
-                        :filename "test.txt"
-                        :size (.length file-fi)}
-        fi-attachment-id (-> (request :post "/api/licenses/add_attachment")
-                             (assoc :params {"file" filecontent-fi})
-                             (assoc :multipart-params {"file" filecontent-fi})
-                             (authenticate api-key owner)
-                             handler
-                             read-ok-body
-                             :id)
-
-        license-id (-> (request :post "/api/licenses/create")
-                       (authenticate api-key owner)
-                       (json-body {:licensetype "attachment"
-                                   ;; TODO different content for different languages
-                                   :localizations {:en {:title "en title"
-                                                        :textcontent "en text"
-                                                        :attachment-id en-attachment-id}
-                                                   :fi {:title "fi title"
-                                                        :textcontent "fi text"
-                                                        :attachment-id fi-attachment-id}}})
-                       handler
-                       read-ok-body
-                       :id)]
-    (testing "submit application"
-      (is (= {:success true}
-             (send-command applicant {:type :application.command/submit
-                                      :application-id app-id}))))
-    (testing "attach license to application"
-      (is (= {:success true}
-             (send-command handler-user {:type :application.command/add-licenses
-                                         :application-id app-id
-                                         :comment ""
-                                         :licenses [license-id]}))))
-    (testing "access license"
-      (testing "as applicant"
-        (is (= "hello from file\n"
-               (-> (request :get (str "/api/applications/" app-id "/license-attachment/" license-id "/en"))
-                   (authenticate api-key applicant)
-                   handler
-                   assert-response-is-ok
-                   :body
-                   slurp)))
-        (testing "in finnish"
-          (is (= "tervehdys tiedostosta\n"
-                 (-> (request :get (str "/api/applications/" app-id "/license-attachment/" license-id "/fi"))
-                     (authenticate api-key applicant)
-                     handler
-                     assert-response-is-ok
-                     :body
-                     slurp)))))
-      (testing "as handler"
-        (is (= "hello from file\n"
-               (-> (request :get (str "/api/applications/" app-id "/license-attachment/" license-id "/en"))
-                   (authenticate api-key handler-user)
-                   handler
-                   assert-response-is-ok
-                   :body
-                   slurp))))
-      (testing "as non-applicant"
-        (is (response-is-forbidden?
-             (-> (request :get (str "/api/applications/" app-id "/license-attachment/" license-id "/en"))
-                 (authenticate api-key non-applicant)
-                 handler)))))))
-
 (deftest test-applications-api-security
   (let [api-key "42"
         applicant "alice"
diff --git a/test/clj/rems/application/test_commands.clj b/test/clj/rems/application/test_commands.clj
index 15e57bf22..39c15637a 100644
--- a/test/clj/rems/application/test_commands.clj
+++ b/test/clj/rems/application/test_commands.clj
@@ -621,28 +621,6 @@
                         :actor handler-user-id
                         :comment "outdated"})))))
 
-(deftest test-revoke
-  (let [application (apply-events nil
-                                  [dummy-created-event
-                                   {:event/type :application.event/submitted
-                                    :event/time test-time
-                                    :event/actor applicant-user-id
-                                    :application/id app-id}
-                                   {:event/type :application.event/approved
-                                    :event/time test-time
-                                    :event/actor handler-user-id
-                                    :application/id app-id
-                                    :application/comment ""}])]
-    (is (= {:event/type :application.event/revoked
-            :event/time test-time
-            :event/actor handler-user-id
-            :application/id app-id
-            :application/comment "license violated"}
-           (ok-command application
-                       {:type :application.command/revoke
-                        :actor handler-user-id
-                        :comment "license violated"})))))
-
 (deftest test-decision
   (let [application (apply-events nil
                                   [dummy-created-event
diff --git a/test/clj/rems/application/test_model.clj b/test/clj/rems/application/test_model.clj
index b7a9c3500..e1c5dd4ab 100644
--- a/test/clj/rems/application/test_model.clj
+++ b/test/clj/rems/application/test_model.clj
@@ -1,9 +1,5 @@
 (ns rems.application.test-model
-  (:require [beautify-web.core :as bw]
-            [clojure.set :as set]
-            [clojure.string :as str]
-            [clojure.test :refer :all]
-            [hiccup.core :as hiccup]
+  (:require [clojure.test :refer :all]
             [rems.api.schema :as schema]
             [rems.application.events :as events]
             [rems.application.model :as model]
@@ -144,19 +140,7 @@
 (def ^:private get-user
   {"applicant" {:eppn "applicant"
                 :mail "applicant@example.com"
-                :commonName "Applicant"}
-   "commenter" {:eppn "commenter"
-                :mail "commenter@example.com"
-                :commonName "Commenter"}
-   "decider" {:eppn "decider"
-              :mail "decider@example.com"
-              :commonName "Decider"}
-   "handler" {:eppn "handler"
-              :mail "handler@example.com"
-              :commonName "Handler"}
-   "member" {:eppn "member"
-             :mail "member@example.com"
-             :commonName "Member"}})
+                :commonName "Applicant"}})
 
 (def ^:private get-users-with-role
   {:owner ["owner1"]
@@ -179,46 +163,6 @@
        :archived false
        :expired false}})
 
-(def ^:private resource-id-11
-  {:resource/id 11
-   :resource/ext-id "urn:11"
-   :catalogue-item/id 10
-   :catalogue-item/title {:en "en title"
-                          :fi "fi title"}
-   :catalogue-item/infourl {:en "http://info.com"}
-   :catalogue-item/start (DateTime. 100)
-   :catalogue-item/end nil
-   :catalogue-item/enabled true
-   :catalogue-item/expired false
-   :catalogue-item/archived false})
-
-(def ^:private resource-id-21
-  {:resource/id 21
-   :resource/ext-id "urn:21"
-   :catalogue-item/id 20
-   :catalogue-item/title {:en "en title"
-                          :fi "fi title"}
-   :catalogue-item/infourl {:en "http://info.com"}
-   :catalogue-item/start (DateTime. 100)
-   :catalogue-item/end nil
-   :catalogue-item/enabled true
-   :catalogue-item/expired false
-   :catalogue-item/archived false})
-
-(def ^:private resource-id-31
-  {:catalogue-item/end nil
-   :catalogue-item/expired false
-   :catalogue-item/enabled true
-   :resource/id 31
-   :catalogue-item/title
-   {:en "en title", :fi "fi title"}
-   :catalogue-item/infourl
-   {:en "http://info.com"}
-   :resource/ext-id "urn:31"
-   :catalogue-item/start (DateTime. 100)
-   :catalogue-item/archived false
-   :catalogue-item/id 30})
-
 ;; no attachments here for now
 (defn ^:private get-attachments-for-application [id]
   [])
@@ -240,36 +184,30 @@
                     :get-users-with-role get-users-with-role
                     :get-workflow get-workflow
                     :get-attachments-for-application get-attachments-for-application}
-        sample-applications (atom [])
-        save-sample-application (fn [app]
-                                  (swap! sample-applications conj app)
-                                  app)
         apply-events (fn [events]
                        (let [application (-> events
                                              events/validate-events
                                              (model/build-application-view injections)
-                                             save-sample-application
                                              permissions/cleanup)]
                          (is (contains? model/states (:application/state application)))
                          application))]
+
     (testing "created"
-      (let [new-event {:event/type :application.event/created
-                       :event/time (DateTime. 1000)
-                       :event/actor "applicant"
-                       :application/id 1
-                       :application/external-id "extid"
-                       :application/resources [{:catalogue-item/id 10
-                                                :resource/ext-id "urn:11"}
-                                               {:catalogue-item/id 20
-                                                :resource/ext-id "urn:21"}]
-                       :application/licenses [{:license/id 30}
-                                              {:license/id 31}
-                                              {:license/id 32}]
-                       :form/id 40
-                       :workflow/id 50
-                       :workflow/type :workflow/dynamic}
-            events [new-event]
-            enriched-events [(assoc new-event :event/actor-attributes (get-user "applicant"))]
+      (let [events [{:event/type :application.event/created
+                     :event/time (DateTime. 1000)
+                     :event/actor "applicant"
+                     :application/id 1
+                     :application/external-id "extid"
+                     :application/resources [{:catalogue-item/id 10
+                                              :resource/ext-id "urn:11"}
+                                             {:catalogue-item/id 20
+                                              :resource/ext-id "urn:21"}]
+                     :application/licenses [{:license/id 30}
+                                            {:license/id 31}
+                                            {:license/id 32}]
+                     :form/id 40
+                     :workflow/id 50
+                     :workflow/type :workflow/dynamic}]
             expected-application {:application/id 1
                                   :application/external-id "extid"
                                   :application/state :application.state/draft
@@ -334,7 +272,7 @@
                                                           :license/enabled true
                                                           :license/archived false}]
                                   :application/accepted-licenses {}
-                                  :application/events enriched-events
+                                  :application/events events
                                   :application/description ""
                                   :application/form {:form/id 40
                                                      :form/title "form title"
@@ -361,18 +299,16 @@
         (is (= expected-application (apply-events events)))
 
         (testing "> draft saved"
-          (let [new-event {:event/type :application.event/draft-saved
-                           :event/time (DateTime. 2000)
-                           :event/actor "applicant"
-                           :application/id 1
-                           :application/field-values {41 "foo"
-                                                      42 "bar"}}
-                events (conj events new-event)
-                enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "applicant")))
+          (let [events (conj events {:event/type :application.event/draft-saved
+                                     :event/time (DateTime. 2000)
+                                     :event/actor "applicant"
+                                     :application/id 1
+                                     :application/field-values {41 "foo"
+                                                                42 "bar"}})
                 expected-application (deep-merge expected-application
                                                  {:application/modified (DateTime. 2000)
                                                   :application/last-activity (DateTime. 2000)
-                                                  :application/events enriched-events
+                                                  :application/events events
                                                   :application/description "foo"
                                                   :application/accepted-licenses {}
                                                   :application/form {:form/fields [{:field/value "foo"}
@@ -380,17 +316,15 @@
             (is (= expected-application (apply-events events)))
 
             (testing "> copied from"
-              (let [new-event {:event/type :application.event/copied-from
-                               :event/time (DateTime. 3000)
-                               :event/actor "applicant"
-                               :application/id 1
-                               :application/copied-from {:application/id 42
-                                                         :application/external-id "2019/42"}}
-                    events (conj events new-event)
-                    enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "applicant")))
+              (let [events (conj events {:event/type :application.event/copied-from
+                                         :event/time (DateTime. 3000)
+                                         :event/actor "applicant"
+                                         :application/id 1
+                                         :application/copied-from {:application/id 42
+                                                                   :application/external-id "2019/42"}})
                     expected-application (deep-merge expected-application
                                                      {:application/last-activity (DateTime. 3000)
-                                                      :application/events enriched-events
+                                                      :application/events events
                                                       :application/copied-from {:application/id 42
                                                                                 :application/external-id "2019/42"}
                                                       :application/form {:form/fields [{:field/previous-value "foo"}
@@ -398,26 +332,23 @@
                 (is (= expected-application (apply-events events)))))
 
             (testing "> copied to"
-              (let [;; two copied-to events in order to test the order in which they are shown
-                    new-event-1 {:event/type :application.event/copied-to
-                                 :event/time (DateTime. 3000)
-                                 :event/actor "applicant"
-                                 :application/id 1
-                                 :application/copied-to {:application/id 666
-                                                         :application/external-id "2020/666"}}
-                    new-event-2 {:event/type :application.event/copied-to
-                                 :event/time (DateTime. 3000)
-                                 :event/actor "applicant"
-                                 :application/id 1
-                                 :application/copied-to {:application/id 777
-                                                         :application/external-id "2021/777"}}
-                    events (conj events new-event-1 new-event-2)
-                    enriched-events (conj enriched-events
-                                          (assoc new-event-1 :event/actor-attributes (get-user "applicant"))
-                                          (assoc new-event-2 :event/actor-attributes (get-user "applicant")))
+              (let [events (conj events
+                                 ;; two copied-to events in order to test the order in which they are shown
+                                 {:event/type :application.event/copied-to
+                                  :event/time (DateTime. 3000)
+                                  :event/actor "applicant"
+                                  :application/id 1
+                                  :application/copied-to {:application/id 666
+                                                          :application/external-id "2020/666"}}
+                                 {:event/type :application.event/copied-to
+                                  :event/time (DateTime. 3000)
+                                  :event/actor "applicant"
+                                  :application/id 1
+                                  :application/copied-to {:application/id 777
+                                                          :application/external-id "2021/777"}})
                     expected-application (deep-merge expected-application
                                                      {:application/last-activity (DateTime. 3000)
-                                                      :application/events enriched-events
+                                                      :application/events events
                                                       :application/copied-to [{:application/id 666
                                                                                :application/external-id "2020/666"}
                                                                               {:application/id 777
@@ -425,43 +356,34 @@
                 (is (= expected-application (apply-events events)))))
 
             (testing "> accepted licenses"
-              (let [new-event {:event/type :application.event/licenses-accepted
-                               :event/time (DateTime. 2500)
-                               :event/actor "applicant"
-                               :application/id 1
-                               :application/accepted-licenses #{30 31 32}}
-                    events (conj events new-event)
-                    enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "applicant")))
+              (let [events (conj events {:event/type :application.event/licenses-accepted
+                                         :event/time (DateTime. 2500)
+                                         :event/actor "applicant"
+                                         :application/id 1
+                                         :application/accepted-licenses #{30 31 32}})
                     expected-application (deep-merge expected-application
                                                      {:application/last-activity (DateTime. 2500)
-                                                      :application/events enriched-events
+                                                      :application/events events
                                                       :application/accepted-licenses {"applicant" #{30 31 32}}})]
                 (is (= expected-application (apply-events events)))
 
                 (testing "> resources changed by applicant"
-                  (let [new-event {:event/type :application.event/resources-changed
-                                   :event/time (DateTime. 2600)
-                                   :event/actor "applicant"
-                                   :application/id 1
-                                   :application/resources [{:catalogue-item/id 10 :resource/ext-id "urn:11"}
-                                                           {:catalogue-item/id 20 :resource/ext-id "urn:21"}
-                                                           {:catalogue-item/id 30 :resource/ext-id "urn:31"}]
-                                   :application/licenses [{:license/id 30}
-                                                          {:license/id 31}
-                                                          {:license/id 32}
-                                                          {:license/id 34}]}
-                        events (conj events new-event)
-                        enriched-events
-                        (conj enriched-events (merge new-event
-                                                     {:event/actor-attributes (get-user "applicant")
-                                                      :application/resources
-                                                      [resource-id-11
-                                                       resource-id-21
-                                                       resource-id-31]}))
+                  (let [events (conj events
+                                     {:event/type :application.event/resources-changed
+                                      :event/time (DateTime. 2600)
+                                      :event/actor "applicant"
+                                      :application/id 1
+                                      :application/resources [{:catalogue-item/id 10 :resource/ext-id "urn:11"}
+                                                              {:catalogue-item/id 20 :resource/ext-id "urn:21"}
+                                                              {:catalogue-item/id 30 :resource/ext-id "urn:31"}]
+                                      :application/licenses [{:license/id 30}
+                                                             {:license/id 31}
+                                                             {:license/id 32}
+                                                             {:license/id 34}]})
                         expected-application (deep-merge expected-application
                                                          {:application/last-activity (DateTime. 2600)
                                                           :application/modified (DateTime. 2600)
-                                                          :application/events enriched-events
+                                                          :application/events events
                                                           :application/resources (conj (:application/resources expected-application)
                                                                                        {:catalogue-item/id 30
                                                                                         :resource/id 31
@@ -488,31 +410,27 @@
                     (is (= expected-application (apply-events events)))))
 
                 (testing "> submitted"
-                  (let [new-event {:event/type :application.event/submitted
-                                   :event/time (DateTime. 3000)
-                                   :event/actor "applicant"
-                                   :application/id 1}
-                        events (conj events new-event)
-                        enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "applicant")))
+                  (let [events (conj events {:event/type :application.event/submitted
+                                             :event/time (DateTime. 3000)
+                                             :event/actor "applicant"
+                                             :application/id 1})
                         expected-application (merge expected-application
                                                     {:application/last-activity (DateTime. 3000)
-                                                     :application/events enriched-events
+                                                     :application/events events
                                                      :application/first-submitted (DateTime. 3000)
                                                      :application/state :application.state/submitted
                                                      :application/todo :new-application})]
                     (is (= expected-application (apply-events events)))
 
                     (testing "> returned"
-                      (let [new-event {:event/type :application.event/returned
-                                       :event/time (DateTime. 4000)
-                                       :event/actor "handler"
-                                       :application/id 1
-                                       :application/comment "fix stuff"}
-                            events (conj events new-event)
-                            enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "handler")))
+                      (let [events (conj events {:event/type :application.event/returned
+                                                 :event/time (DateTime. 4000)
+                                                 :event/actor "handler"
+                                                 :application/id 1
+                                                 :application/comment "fix stuff"})
                             expected-application (deep-merge expected-application
                                                              {:application/last-activity (DateTime. 4000)
-                                                              :application/events enriched-events
+                                                              :application/events events
                                                               :application/state :application.state/returned
                                                               :application/todo nil
                                                               :application/form {:form/fields [{:field/previous-value "foo"}
@@ -520,27 +438,24 @@
                         (is (= expected-application (apply-events events)))
 
                         (testing "> draft saved x2"
-                          (let [new-event-1 {:event/type :application.event/draft-saved
-                                             :event/time (DateTime. 5000)
-                                             :event/actor "applicant"
-                                             :application/id 1
-                                             ;; non-submitted versions should not show up as the previous value
-                                             :application/field-values {41 "intermediate draft"
-                                                                        42 "intermediate draft"}}
-                                new-event-2 {:event/type :application.event/draft-saved
-                                             :event/time (DateTime. 6000)
-                                             :event/actor "applicant"
-                                             :application/id 1
-                                             :application/field-values {41 "new foo"
-                                                                        42 "new bar"}}
-                                events (conj events new-event-1 new-event-2)
-                                enriched-events (conj enriched-events
-                                                      (assoc new-event-1 :event/actor-attributes (get-user "applicant"))
-                                                      (assoc new-event-2 :event/actor-attributes (get-user "applicant")))
+                          (let [events (conj events
+                                             {:event/type :application.event/draft-saved
+                                              :event/time (DateTime. 5000)
+                                              :event/actor "applicant"
+                                              :application/id 1
+                                              ;; non-submitted versions should not show up as the previous value
+                                              :application/field-values {41 "intermediate draft"
+                                                                         42 "intermediate draft"}}
+                                             {:event/type :application.event/draft-saved
+                                              :event/time (DateTime. 6000)
+                                              :event/actor "applicant"
+                                              :application/id 1
+                                              :application/field-values {41 "new foo"
+                                                                         42 "new bar"}})
                                 expected-application (deep-merge expected-application
                                                                  {:application/modified (DateTime. 6000)
                                                                   :application/last-activity (DateTime. 6000)
-                                                                  :application/events enriched-events
+                                                                  :application/events events
                                                                   :application/description "new foo"
                                                                   :application/form {:form/fields [{:field/value "new foo"
                                                                                                     :field/previous-value "foo"}
@@ -549,29 +464,27 @@
                             (is (= expected-application (apply-events events)))
 
                             (testing "> submitted"
-                              (let [new-event {:event/type :application.event/submitted
-                                               :event/time (DateTime. 7000)
-                                               :event/actor "applicant"
-                                               :application/id 1}
-                                    events (conj events new-event)
-                                    enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "applicant")))
+                              (let [events (conj events
+                                                 {:event/type :application.event/submitted
+                                                  :event/time (DateTime. 7000)
+                                                  :event/actor "applicant"
+                                                  :application/id 1})
                                     expected-application (merge expected-application
                                                                 {:application/last-activity (DateTime. 7000)
-                                                                 :application/events enriched-events
+                                                                 :application/events events
                                                                  :application/state :application.state/submitted
                                                                  :application/todo :resubmitted-application})]
                                 (is (= expected-application (apply-events events)))))))
 
                         (testing "> submitted (no draft saved)"
-                          (let [new-event {:event/type :application.event/submitted
-                                           :event/time (DateTime. 7000)
-                                           :event/actor "applicant"
-                                           :application/id 1}
-                                events (conj events new-event)
-                                enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "applicant")))
+                          (let [events (conj events
+                                             {:event/type :application.event/submitted
+                                              :event/time (DateTime. 7000)
+                                              :event/actor "applicant"
+                                              :application/id 1})
                                 expected-application (deep-merge expected-application
                                                                  {:application/last-activity (DateTime. 7000)
-                                                                  :application/events enriched-events
+                                                                  :application/events events
                                                                   :application/state :application.state/submitted
                                                                   :application/todo :resubmitted-application
                                                                   ;; when there was no draft-saved event, the current and
@@ -583,30 +496,23 @@
                             (is (= expected-application (apply-events events)))))))
 
                     (testing "> resources changed by handler"
-                      (let [new-event {:event/type :application.event/resources-changed
-                                       :event/time (DateTime. 3400)
-                                       :event/actor "handler"
-                                       :application/id 1
-                                       :application/comment "You should include this resource."
-                                       :application/resources [{:catalogue-item/id 10 :resource/ext-id "urn:11"}
-                                                               {:catalogue-item/id 20 :resource/ext-id "urn:21"}
-                                                               {:catalogue-item/id 30 :resource/ext-id "urn:31"}]
-                                       :application/licenses [{:license/id 30}
-                                                              {:license/id 31}
-                                                              {:license/id 32}
-                                                              {:license/id 34}]}
-                            events (conj events new-event)
-                            enriched-events
-                            (conj enriched-events (merge new-event
-                                                         {:event/actor-attributes (get-user "handler")
-                                                          :application/resources
-                                                          [resource-id-11
-                                                           resource-id-21
-                                                           resource-id-31]}))
+                      (let [events (conj events
+                                         {:event/type :application.event/resources-changed
+                                          :event/time (DateTime. 3400)
+                                          :event/actor "handler"
+                                          :application/id 1
+                                          :application/comment "You should include this resource."
+                                          :application/resources [{:catalogue-item/id 10 :resource/ext-id "urn:11"}
+                                                                  {:catalogue-item/id 20 :resource/ext-id "urn:21"}
+                                                                  {:catalogue-item/id 30 :resource/ext-id "urn:31"}]
+                                          :application/licenses [{:license/id 30}
+                                                                 {:license/id 31}
+                                                                 {:license/id 32}
+                                                                 {:license/id 34}]})
                             expected-application (deep-merge expected-application
                                                              {:application/last-activity (DateTime. 3400)
                                                               :application/modified (DateTime. 3400)
-                                                              :application/events enriched-events
+                                                              :application/events events
                                                               :application/resources (conj (:application/resources expected-application)
                                                                                            {:catalogue-item/id 30
                                                                                             :resource/id 31
@@ -632,18 +538,17 @@
                                                                                            :license/archived false})})]
                         (is (= expected-application (apply-events events)))))
                     (testing "> licenses added"
-                      (let [new-event {:event/type :application.event/licenses-added
-                                       :event/time (DateTime. 3500)
-                                       :event/actor "handler"
-                                       :application/id 1
-                                       :application/licenses [{:license/id 33}]
-                                       :application/comment "Please sign these terms also"}
-                            events (conj events new-event)
-                            enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "handler")))
+                      (let [events (conj events
+                                         {:event/type :application.event/licenses-added
+                                          :event/time (DateTime. 3500)
+                                          :event/actor "handler"
+                                          :application/id 1
+                                          :application/licenses [{:license/id 33}]
+                                          :application/comment "Please sign these terms also"})
                             expected-application (merge expected-application
                                                         {:application/last-activity (DateTime. 3500)
                                                          :application/modified (DateTime. 3500)
-                                                         :application/events enriched-events
+                                                         :application/events events
                                                          :application/licenses (conj (:application/licenses expected-application)
                                                                                      {:license/id 33
                                                                                       :license/type :attachment
@@ -658,45 +563,38 @@
                         (is (= expected-application (apply-events events)))
 
                         (testing "> approved"
-                          (let [new-event {:event/type :application.event/approved
-                                           :event/time (DateTime. 4000)
-                                           :event/actor "handler"
-                                           :application/id 1
-                                           :application/comment "looks good"}
-                                events (conj events new-event)
-                                enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "handler")))
+                          (let [events (conj events
+                                             {:event/type :application.event/approved
+                                              :event/time (DateTime. 4000)
+                                              :event/actor "handler"
+                                              :application/id 1
+                                              :application/comment "looks good"})
                                 expected-application (merge expected-application
                                                             {:application/last-activity (DateTime. 4000)
-                                                             :application/events enriched-events
+                                                             :application/events events
                                                              :application/state :application.state/approved
                                                              :application/todo nil})]
                             (is (= expected-application (apply-events events)))
 
                             (testing "> resources changed by handler"
-                              (let [new-event {:event/type :application.event/resources-changed
-                                               :event/time (DateTime. 4500)
-                                               :event/actor "handler"
-                                               :application/id 1
-                                               :application/comment "I changed the resources"
-                                               :application/resources [{:catalogue-item/id 10 :resource/ext-id "urn:11"}
-                                                                       {:catalogue-item/id 30 :resource/ext-id "urn:31"}]
-                                               :application/licenses [{:license/id 30}
-                                                                      {:license/id 31}
-                                                                      {:license/id 32}
-                                                                      ;; Include also the previously added license #33 in the new licenses.
-                                                                      {:license/id 33}
-                                                                      {:license/id 34}]}
-                                    events (conj events new-event)
-                                    enriched-events
-                                    (conj enriched-events (merge new-event
-                                                                 {:event/actor-attributes (get-user "handler")
-                                                                  :application/resources
-                                                                  [resource-id-11
-                                                                   resource-id-31]}))
+                              (let [events (conj events
+                                                 {:event/type :application.event/resources-changed
+                                                  :event/time (DateTime. 4500)
+                                                  :event/actor "handler"
+                                                  :application/id 1
+                                                  :application/comment "I changed the resources"
+                                                  :application/resources [{:catalogue-item/id 10 :resource/ext-id "urn:11"}
+                                                                          {:catalogue-item/id 30 :resource/ext-id "urn:31"}]
+                                                  :application/licenses [{:license/id 30}
+                                                                         {:license/id 31}
+                                                                         {:license/id 32}
+                                                                         ;; Include also the previously added license #33 in the new licenses.
+                                                                         {:license/id 33}
+                                                                         {:license/id 34}]})
                                     expected-application (deep-merge expected-application
                                                                      {:application/last-activity (DateTime. 4500)
                                                                       :application/modified (DateTime. 4500)
-                                                                      :application/events enriched-events
+                                                                      :application/events events
                                                                       :application/resources [{:catalogue-item/id 10
                                                                                                :resource/id 11
                                                                                                :resource/ext-id "urn:11"
@@ -733,315 +631,227 @@
                                 (is (= expected-application (apply-events events)))))
 
                             (testing "> licenses accepted"
-                              (let [new-event {:event/type :application.event/licenses-accepted
-                                               :event/time (DateTime. 4500)
-                                               :event/actor "applicant"
-                                               :application/id 1
-                                               :application/accepted-licenses #{30 31 32 33}}
-                                    events (conj events new-event)
-                                    enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "applicant")))
+                              (let [events (conj events
+                                                 {:event/type :application.event/licenses-accepted
+                                                  :event/time (DateTime. 4500)
+                                                  :event/actor "applicant"
+                                                  :application/id 1
+                                                  :application/accepted-licenses #{30 31 32 33}})
                                     expected-application (merge expected-application
                                                                 {:application/last-activity (DateTime. 4500)
-                                                                 :application/events enriched-events
+                                                                 :application/events events
                                                                  :application/accepted-licenses {"applicant" #{30 31 32 33}}})]
                                 (is (= expected-application (apply-events events)))
 
                                 (testing "> member added"
-                                  (let [new-event {:event/type :application.event/member-added
-                                                   :event/time (DateTime. 4600)
-                                                   :event/actor "handler"
-                                                   :application/id 1
-                                                   :application/member {:userid "member"}}
-                                        events (conj events new-event)
-                                        enriched-events
-                                        (conj enriched-events (merge new-event
-                                                                     {:event/actor-attributes (get-user "handler")
-                                                                      :application/member (get-user "member")}))
+                                  (let [events (conj events
+                                                     {:event/type :application.event/member-added
+                                                      :event/time (DateTime. 4600)
+                                                      :event/actor "handler"
+                                                      :application/id 1
+                                                      :application/member {:userid "member"}})
                                         expected-application (merge expected-application
                                                                     {:application/last-activity (DateTime. 4600)
-                                                                     :application/events enriched-events
-                                                                     :application/members #{{:userid "member", :eppn "member", :mail "member@example.com", :commonName "Member"}}})]
+                                                                     :application/events events
+                                                                     :application/members #{{:userid "member"}}})]
                                     (is (= expected-application (apply-events events)))
                                     (testing "> licenses accepted for new member"
-                                      (let [new-event {:event/type :application.event/licenses-accepted
-                                                       :event/time (DateTime. 4700)
-                                                       :event/actor "member"
-                                                       :application/id 1
-                                                       :application/accepted-licenses #{30 33}}
-                                            events (conj events new-event)
-                                            enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "member")))
+                                      (let [events (conj events
+                                                         {:event/type :application.event/licenses-accepted
+                                                          :event/time (DateTime. 4700)
+                                                          :event/actor "member"
+                                                          :application/id 1
+                                                          :application/accepted-licenses #{30 33}})
                                             expected-application (merge expected-application
                                                                         {:application/last-activity (DateTime. 4700)
-                                                                         :application/events enriched-events
+                                                                         :application/events events
                                                                          :application/accepted-licenses {"applicant" #{30 31 32 33}
                                                                                                          "member" #{30 33}}})]
                                         (is (= expected-application (apply-events events)))
                                         (testing "> licenses accepted overwrites previous"
-                                          (let [new-event {:event/type :application.event/licenses-accepted
-                                                           :event/time (DateTime. 4800)
-                                                           :event/actor "member"
-                                                           :application/id 1
-                                                           :application/accepted-licenses #{31 32}}
-                                                events (conj events new-event)
-                                                enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "member")))
+                                          (let [events (conj events
+                                                             {:event/type :application.event/licenses-accepted
+                                                              :event/time (DateTime. 4800)
+                                                              :event/actor "member"
+                                                              :application/id 1
+                                                              :application/accepted-licenses #{31 32}})
                                                 expected-application (merge expected-application
                                                                             {:application/last-activity (DateTime. 4800)
-                                                                             :application/events enriched-events
+                                                                             :application/events events
                                                                              :application/accepted-licenses {"applicant" #{30 31 32 33}
                                                                                                              "member" #{31 32}}})]
                                             (is (= expected-application (apply-events events)))))))))
 
 
                                 (testing "> closed"
-                                  (let [new-event {:event/type :application.event/closed
-                                                   :event/time (DateTime. 5000)
-                                                   :event/actor "handler"
-                                                   :application/id 1
-                                                   :application/comment "the project is finished"}
-                                        events (conj events new-event)
-                                        enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "handler")))
+                                  (let [events (conj events
+                                                     {:event/type :application.event/closed
+                                                      :event/time (DateTime. 5000)
+                                                      :event/actor "handler"
+                                                      :application/id 1
+                                                      :application/comment "the project is finished"})
                                         expected-application (merge expected-application
                                                                     {:application/last-activity (DateTime. 5000)
-                                                                     :application/events enriched-events
+                                                                     :application/events events
                                                                      :application/state :application.state/closed
                                                                      :application/todo nil})]
-                                    (is (= expected-application (apply-events events)))))
-
-                                (testing "> revoked"
-                                  (let [new-event {:event/type :application.event/revoked
-                                                   :event/time (DateTime. 5000)
-                                                   :event/actor "handler"
-                                                   :application/id 1
-                                                   :application/comment "license terms were violated"}
-                                        events (conj events new-event)
-                                        enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "handler")))
-                                        expected-application (merge expected-application
-                                                                    {:application/last-activity (DateTime. 5000)
-                                                                     :application/events enriched-events
-                                                                     :application/state :application.state/revoked
-                                                                     :application/todo nil})]
                                     (is (= expected-application (apply-events events)))))))))
 
                         (testing "> rejected"
-                          (let [new-event {:event/type :application.event/rejected
-                                           :event/time (DateTime. 4000)
-                                           :event/actor "handler"
-                                           :application/id 1
-                                           :application/comment "never gonna happen"}
-                                events (conj events new-event)
-                                enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "handler")))
+                          (let [events (conj events
+                                             {:event/type :application.event/rejected
+                                              :event/time (DateTime. 4000)
+                                              :event/actor "handler"
+                                              :application/id 1
+                                              :application/comment "never gonna happen"})
                                 expected-application (merge expected-application
                                                             {:application/last-activity (DateTime. 4000)
-                                                             :application/events enriched-events
+                                                             :application/events events
                                                              :application/state :application.state/rejected
                                                              :application/todo nil})]
                             (is (= expected-application (apply-events events)))))
 
                         (testing "> comment requested"
                           (let [request-id (UUID/fromString "4de6c2b0-bb2e-4745-8f92-bd1d1f1e8298")
-                                new-event {:event/type :application.event/comment-requested
-                                           :event/time (DateTime. 4000)
-                                           :event/actor "handler"
-                                           :application/id 1
-                                           :application/request-id request-id
-                                           :application/commenters ["commenter"]
-                                           :application/comment "please comment"}
-                                events (conj events new-event)
-                                enriched-events (conj enriched-events (merge new-event
-                                                                             {:event/actor-attributes (get-user "handler")
-                                                                              :application/commenters [(get-user "commenter")]}))
+                                events (conj events
+                                             {:event/type :application.event/comment-requested
+                                              :event/time (DateTime. 4000)
+                                              :event/actor "handler"
+                                              :application/id 1
+                                              :application/request-id request-id
+                                              :application/commenters ["commenter"]
+                                              :application/comment "please comment"})
                                 expected-application (deep-merge expected-application
                                                                  {:application/last-activity (DateTime. 4000)
-                                                                  :application/events enriched-events
+                                                                  :application/events events
                                                                   :application/todo :waiting-for-review
                                                                   :rems.application.model/latest-comment-request-by-user {"commenter" request-id}})]
                             (is (= expected-application (apply-events events)))
 
                             (testing "> commented"
-                              (let [new-event {:event/type :application.event/commented
-                                               :event/time (DateTime. 5000)
-                                               :event/actor "commenter"
-                                               :application/id 1
-                                               :application/request-id request-id
-                                               :application/comment "looks good"}
-                                    events (conj events new-event)
-                                    enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "commenter")))
+                              (let [events (conj events
+                                                 {:event/type :application.event/commented
+                                                  :event/time (DateTime. 5000)
+                                                  :event/actor "commenter"
+                                                  :application/id 1
+                                                  :application/request-id request-id
+                                                  :application/comment "looks good"})
                                     expected-application (merge expected-application
                                                                 {:application/last-activity (DateTime. 5000)
-                                                                 :application/events enriched-events
+                                                                 :application/events events
                                                                  :application/todo :no-pending-requests
                                                                  :rems.application.model/latest-comment-request-by-user {}})]
                                 (is (= expected-application (apply-events events)))))))
 
                         (testing "> decision requested"
                           (let [request-id (UUID/fromString "db9c7fd6-53be-4b04-b15d-a3a8e0a45e49")
-                                new-event {:event/type :application.event/decision-requested
-                                           :event/time (DateTime. 4000)
-                                           :event/actor "handler"
-                                           :application/id 1
-                                           :application/request-id request-id
-                                           :application/deciders ["decider"]
-                                           :application/comment "please decide"}
-                                events (conj events new-event)
-                                enriched-events (conj enriched-events (merge new-event
-                                                                             {:event/actor-attributes (get-user "handler")
-                                                                              :application/deciders [(get-user "decider")]}))
+                                events (conj events
+                                             {:event/type :application.event/decision-requested
+                                              :event/time (DateTime. 4000)
+                                              :event/actor "handler"
+                                              :application/id 1
+                                              :application/request-id request-id
+                                              :application/deciders ["decider"]
+                                              :application/comment "please decide"})
                                 expected-application (merge expected-application
                                                             {:application/last-activity (DateTime. 4000)
-                                                             :application/events enriched-events
+                                                             :application/events events
                                                              :application/todo :waiting-for-decision
                                                              :rems.application.model/latest-decision-request-by-user {"decider" request-id}})]
                             (is (= expected-application (apply-events events)))
 
                             (testing "> decided"
-                              (let [new-event {:event/type :application.event/decided
-                                               :event/time (DateTime. 5000)
-                                               :event/actor "decider"
-                                               :application/id 1
-                                               :application/request-id request-id
-                                               :application/decision :approved
-                                               :application/comment "I approve this"}
-                                    events (conj events new-event)
-                                    enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "decider")))
+                              (let [events (conj events
+                                                 {:event/type :application.event/decided
+                                                  :event/time (DateTime. 5000)
+                                                  :event/actor "decider"
+                                                  :application/id 1
+                                                  :application/request-id request-id
+                                                  :application/decision :approved
+                                                  :application/comment "I approve this"})
                                     expected-application (merge expected-application
                                                                 {:application/last-activity (DateTime. 5000)
-                                                                 :application/events enriched-events
+                                                                 :application/events events
                                                                  :application/todo :no-pending-requests
                                                                  :rems.application.model/latest-decision-request-by-user {}})]
                                 (is (= expected-application (apply-events events)))))))
 
                         (testing "> member invited"
                           (let [token "b187bda7b9da9053a5d8b815b029e4ba"
-                                new-event {:event/type :application.event/member-invited
-                                           :event/time (DateTime. 4000)
-                                           :event/actor "applicant"
-                                           :application/id 1
-                                           :application/member {:name "Mr. Member"
-                                                                :email "member@example.com"}
-                                           :invitation/token token}
-                                events (conj events new-event)
-                                enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "applicant")))
+                                events (conj events
+                                             {:event/type :application.event/member-invited
+                                              :event/time (DateTime. 4000)
+                                              :event/actor "applicant"
+                                              :application/id 1
+                                              :application/member {:name "Mr. Member"
+                                                                   :email "member@example.com"}
+                                              :invitation/token token})
                                 expected-application (deep-merge expected-application
                                                                  {:application/last-activity (DateTime. 4000)
-                                                                  :application/events enriched-events
+                                                                  :application/events events
                                                                   :application/invitation-tokens {token {:name "Mr. Member"
                                                                                                          :email "member@example.com"}}})]
                             (is (= expected-application (apply-events events)))
 
                             (testing "> member uninvited"
-                              (let [new-event {:event/type :application.event/member-uninvited
-                                               :event/time (DateTime. 5000)
-                                               :event/actor "applicant"
-                                               :application/id 1
-                                               :application/member {:name "Mr. Member"
-                                                                    :email "member@example.com"}
-                                               :application/comment "he left the project"}
-                                    events (conj events new-event)
-                                    enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "applicant")))
+                              (let [events (conj events
+                                                 {:event/type :application.event/member-uninvited
+                                                  :event/time (DateTime. 5000)
+                                                  :event/actor "applicant"
+                                                  :application/id 1
+                                                  :application/member {:name "Mr. Member"
+                                                                       :email "member@example.com"}
+                                                  :application/comment "he left the project"})
                                     expected-application (merge expected-application
                                                                 {:application/last-activity (DateTime. 5000)
-                                                                 :application/events enriched-events
+                                                                 :application/events events
                                                                  :application/invitation-tokens {}})]
                                 (is (= expected-application (apply-events events)))))
 
                             (testing "> member joined"
-                              (let [new-event {:event/type :application.event/member-joined
-                                               :event/time (DateTime. 5000)
-                                               :event/actor "member"
-                                               :application/id 1
-                                               :invitation/token token}
-                                    events (conj events new-event)
-                                    enriched-events (conj enriched-events (assoc new-event :event/actor-attributes (get-user "member")))
+                              (let [events (conj events
+                                                 {:event/type :application.event/member-joined
+                                                  :event/time (DateTime. 5000)
+                                                  :event/actor "member"
+                                                  :application/id 1
+                                                  :invitation/token token})
                                     expected-application (merge expected-application
                                                                 {:application/last-activity (DateTime. 5000)
-                                                                 :application/events enriched-events
-                                                                 :application/members #{{:userid "member", :eppn "member", :mail "member@example.com", :commonName "Member"}}
+                                                                 :application/events events
+                                                                 :application/members #{{:userid "member"}}
                                                                  :application/invitation-tokens {}})]
                                 (is (= expected-application (apply-events events)))))))
 
                         (testing "> member added"
-                          (let [new-event {:event/type :application.event/member-added
-                                           :event/time (DateTime. 4000)
-                                           :event/actor "handler"
-                                           :application/id 1
-                                           :application/member {:userid "member"}}
-                                events (conj events new-event)
-                                enriched-events (conj enriched-events (merge new-event
-                                                                             {:event/actor-attributes (get-user "handler")
-                                                                              :application/member (get-user "member")}))
+                          (let [events (conj events
+                                             {:event/type :application.event/member-added
+                                              :event/time (DateTime. 4000)
+                                              :event/actor "handler"
+                                              :application/id 1
+                                              :application/member {:userid "member"}})
                                 expected-application (merge expected-application
                                                             {:application/last-activity (DateTime. 4000)
-                                                             :application/events enriched-events
-                                                             :application/members #{{:userid "member", :eppn "member", :mail "member@example.com", :commonName "Member"}}})]
+                                                             :application/events events
+                                                             :application/members #{{:userid "member"}}})]
                             (is (= expected-application (apply-events events)))
 
                             (testing "> member removed"
-                              (let [new-event {:event/type :application.event/member-removed
-                                               :event/time (DateTime. 5000)
-                                               :event/actor "applicant"
-                                               :application/id 1
-                                               :application/member {:userid "member"}
-                                               :application/comment "he left the project"}
-                                    events (conj events new-event)
-                                    enriched-events (conj enriched-events (merge new-event
-                                                                                 {:event/actor-attributes (get-user "applicant")
-                                                                                  :application/member (get-user "member")}))
+                              (let [events (conj events
+                                                 {:event/type :application.event/member-removed
+                                                  :event/time (DateTime. 5000)
+                                                  :event/actor "applicant"
+                                                  :application/id 1
+                                                  :application/member {:userid "member"}
+                                                  :application/comment "he left the project"})
                                     expected-application (merge expected-application
                                                                 {:application/last-activity (DateTime. 5000)
-                                                                 :application/events enriched-events
+                                                                 :application/events events
                                                                  :application/members #{}
                                                                  :application/past-members #{{:userid "member"}}})]
-                                (is (= expected-application (apply-events events)))))))))))))))))
-
-    (testing "generate report: permissions by role and state"
-      (let [apps @sample-applications
-            state-role-permissions (fn [app]
-                                     (map (fn [[role permissions]]
-                                            {:state (:application/state app)
-                                             :role role
-                                             :permissions permissions})
-                                          (:rems.permissions/role-permissions app)))
-            data (mapcat state-role-permissions apps)
-            states (->> data (map :state) distinct) ; keep states in the order they appear in the tests
-            roles (->> data (map :role) distinct sort)
-            nowrap (fn [s]
-                     ;; GitHub will strip all CSS from markdown, so we cannot use CSS for nowrap
-                     (-> s
-                         (str/replace " " "\u00A0") ;  non-breaking space
-                         (str/replace "-" "\u2011")))] ; non-breaking hyphen
-        (->> (hiccup/html
-              "# Application Permissions Reference\n\n"
-              [:table {:border 1}
-               [:tr
-                [:th (nowrap "State \\ Role")]
-                (for [role roles]
-                  [:th (nowrap (name role))])]
-               (for [state states]
-                 [:tr
-                  [:th {:valign :top}
-                   (nowrap (name state))]
-                  (for [role roles]
-                    (let [perm-sets (->> data
-                                         (filter #(= state (:state %)))
-                                         (filter #(= role (:role %)))
-                                         (map :permissions))
-                          all-perms (apply set/union perm-sets)
-                          ;; the states and permissions are not guaranteed to have a 1:1 mapping,
-                          ;; so we separate conditional permissions from those that are always there
-                          always-perms (if (empty? perm-sets)
-                                         #{}
-                                         (apply set/intersection perm-sets))
-                          sometimes-perms (set/difference all-perms always-perms)]
-                      [:td {:valign :top}
-                       "<!-- role: " (name role) " -->"
-                       (for [perm (sort always-perms)]
-                         [:div (nowrap (name perm))])
-                       (for [perm (sort sometimes-perms)]
-                         [:div [:i "(" (nowrap (name perm)) ")"]])]))])])
-             (bw/beautify-html)
-             (spit "docs/application-permissions.md"))))))
+                                (is (= expected-application (apply-events events)))))))))))))))))))
 
 (deftest test-calculate-permissions
+  ;; TODO: is this what we want? wouldn't it be useful to be able to write more than one comment?
   (testing "commenter may comment only once"
     (let [requested (reduce model/calculate-permissions nil [{:event/type :application.event/created
                                                               :event/actor "applicant"}
diff --git a/test/clj/rems/db/test_blacklist.clj b/test/clj/rems/db/test_blacklist.clj
new file mode 100644
index 000000000..716e46434
--- /dev/null
+++ b/test/clj/rems/db/test_blacklist.clj
@@ -0,0 +1,54 @@
+(ns ^:integration rems.db.test-blacklist
+  (:require [clj-time.core :as time]
+            [clojure.test :refer :all]
+            [rems.db.blacklist :as blacklist]
+            [rems.db.testing :refer [test-db-fixture rollback-db-fixture]])
+  (:import org.joda.time.DateTime))
+
+(use-fixtures
+  :once
+  test-db-fixture
+  rollback-db-fixture)
+
+(deftest test-blacklist-event-storage
+  (blacklist/add-event! {:event/type :blacklist.event/add
+                         :event/actor "handler"
+                         :event/time (time/date-time 2019 1 2 8 0 0)
+                         :blacklist/user "baddie"
+                         :blacklist/resource "urn.fi/123"
+                         :event/comment nil})
+  (blacklist/add-event! {:event/type :blacklist.event/remove
+                         :event/actor "handler"
+                         :event/time (time/date-time 2019 2 3 9 0 0)
+                         :blacklist/user "baddie"
+                         :blacklist/resource "urn.fi/123"
+                         :event/comment "it was ok"})
+  (blacklist/add-event! {:event/type :blacklist.event/add
+                         :event/actor "handler"
+                         :event/time (time/date-time 2019 1 1 1 0 0)
+                         :blacklist/user "goodie"
+                         :blacklist/resource "urn.fi/124"
+                         :event/comment nil})
+  (is (= [{:event/id 1
+           :event/type :blacklist.event/add
+           :event/time (time/date-time 2019 1 2 8 0 0)
+           :event/actor "handler"
+           :blacklist/user "baddie"
+           :blacklist/resource "urn.fi/123"
+           :event/comment nil}
+          {:event/id 2
+           :event/type :blacklist.event/remove
+           :event/time (time/date-time 2019 2 3 9 0 0)
+           :event/actor "handler"
+           :blacklist/user "baddie"
+           :blacklist/resource "urn.fi/123"
+           :event/comment "it was ok"}]
+         (blacklist/get-events {:blacklist/resource "urn.fi/123"})))
+  (is (= [{:event/id 3
+           :event/type :blacklist.event/add
+           :event/time (time/date-time 2019 01 01 01)
+           :event/actor "handler"
+           :blacklist/user "goodie"
+           :blacklist/resource "urn.fi/124"
+           :event/comment nil}]
+         (blacklist/get-events {:blacklist/user "goodie"}))))
diff --git a/test/clj/rems/db/test_entitlements.clj b/test/clj/rems/db/test_entitlements.clj
index 313d060ef..9e7bd0230 100644
--- a/test/clj/rems/db/test_entitlements.clj
+++ b/test/clj/rems/db/test_entitlements.clj
@@ -241,35 +241,4 @@
                   data (map #(assoc % :body (cheshire/parse-string (get-in % [:body "postData"]) keyword)) data)]
               (is (= [{:path "/remove" :body [{:resource "resource1" :application app-id :user "bob" :mail "b@o.b"}]}
                       {:path "/remove" :body [{:resource "resource3" :application app-id :user "bob" :mail "b@o.b"}]}]
-                     (map #(select-keys % [:path :body]) data))))))))
-
-    (let [app-id (test-data/create-application! {:actor applicant :catalogue-item-ids [item1]})]
-      (test-data/command! {:type :application.command/accept-licenses
-                           :application-id app-id
-                           :accepted-licenses [lic-id1 lic-id2]
-                           :actor applicant})
-      (test-data/command! {:type :application.command/submit
-                           :application-id app-id
-                           :actor applicant})
-      (test-data/command! {:type :application.command/approve
-                           :application-id app-id
-                           :actor admin
-                           :comment ""})
-      (entitlements-poller/run)
-
-      (testing "revoked application should end entitlements"
-        (with-stub-server server
-          (test-data/command! {:type :application.command/revoke
-                               :application-id app-id
-                               :actor admin
-                               :comment "Banned"})
-          (entitlements-poller/run)
-
-          (is (= 1 (count (stub/recorded-requests server))))
-          (testing "db"
-            (is (= [] (db/get-entitlements {:application app-id :is-active? true}))))
-          (testing "POST"
-            (let [data (stub/recorded-requests server)
-                  data (map #(assoc % :body (cheshire/parse-string (get-in % [:body "postData"]) keyword)) data)]
-              (is (= [{:path "/remove" :body [{:resource "resource1" :application app-id :user "bob" :mail "b@o.b"}]}]
                      (map #(select-keys % [:path :body]) data))))))))))
diff --git a/test/clj/rems/poller/test_email.clj b/test/clj/rems/poller/test_email.clj
index 71157043a..536123007 100644
--- a/test/clj/rems/poller/test_email.clj
+++ b/test/clj/rems/poller/test_email.clj
@@ -5,7 +5,8 @@
             [rems.config]
             [rems.db.user-settings :as user-settings]
             [rems.locales]
-            [rems.poller.email :refer :all]))
+            [rems.poller.email :refer :all]
+            [rems.text :as text]))
 
 (use-fixtures
   :once
@@ -275,17 +276,6 @@
                               :event/type :application.event/rejected
                               :event/actor "handler"}))))
 
-(deftest test-revoked
-  (is (= [{:to-user "applicant"
-           :subject "Your application 2001/3, \"Application title\" has been revoked",
-           :body "Dear Alice Applicant,\n\nYour application 2001/3, \"Application title\" has been revoked.\n\nYou can view the application at http://example.com/application/7\n\nPlease do not reply to this automatically generated message."}
-          {:to-user "assistant"
-           :subject "(2001/3, \"Application title\") Application has been revoked",
-           :body "Dear assistant,\n\nHannah Handler has revoked the application 2001/3, \"Application title\" from Alice Applicant.\n\nYou can view the application at http://example.com/application/7\n\nPlease do not reply to this automatically generated message."}]
-         (emails base-events {:application/id 7
-                              :event/type :application.event/revoked
-                              :event/actor "handler"}))))
-
 (deftest test-id-field
   (with-redefs [rems.config/env (assoc rems.config/env :application-id-column :id)]
     (is (= {:to-user "assistant"
diff --git a/test/clj/rems/test_text.clj b/test/clj/rems/test_text.clj
index 4797538ed..67b4232ab 100644
--- a/test/clj/rems/test_text.clj
+++ b/test/clj/rems/test_text.clj
@@ -45,10 +45,7 @@
                 (sort))))
     (with-language :en
       (fn []
-        (is (not (valid-localization? (localize-event {:event/type "foobar"}))))
+        (is (not (valid-localization? (localize-event "foobar"))))
         (doseq [event-type event-types]
           (testing event-type
-            (is (valid-localization? (localize-event
-                                      (merge {:event/type event-type}
-                                             (when (= event-type :application.event/decided)
-                                               {:application/decision :approved})))))))))))
+            (is (valid-localization? (localize-event event-type)))))))))