diff --git a/defaults/main.yml b/defaults/main.yml index d0bd3ee..9b91a55 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -72,6 +72,15 @@ listmonk_lists: description: >- One short email per premiere, plus the monthly essay. Nothing else. +# API user for the www membership app (Authorization: token user:token). +# It can read and manage subscribers on listmonk_api_lists only. Empty token: +# no API user. listmonk caches API users at startup; the role restarts it. +listmonk_api_user: membership +listmonk_api_token: "" +listmonk_api_role: Membership sync +listmonk_api_lists: + - eom.dev + # Drop the sample lists, subscribers and campaign created by --install. listmonk_remove_sample_data: true diff --git a/playbooks/listmonk.yaml b/playbooks/listmonk.yaml index 003468b..0876e8e 100644 --- a/playbooks/listmonk.yaml +++ b/playbooks/listmonk.yaml @@ -11,3 +11,5 @@ vars: listmonk_db_password: "{{ listmonk_postgresql_password }}" listmonk_oidc_client_secret: "{{ listmonk_dex_client_secret }}" + # Same value as LISTMONK_API_TOKEN in the www "membership" Secret. + listmonk_api_token: "{{ listmonk_membership_api_token }}" diff --git a/templates/configure.sql.j2 b/templates/configure.sql.j2 index 0144e0f..20e219b 100644 --- a/templates/configure.sql.j2 +++ b/templates/configure.sql.j2 @@ -103,6 +103,52 @@ WITH upd AS ( SELECT count(*) FROM upd; {% endfor %} +{% if listmonk_api_token | length > 0 %} +{#- API user for the membership app: subscribers on listmonk_api_lists only. #} +WITH ins AS ( + INSERT INTO roles (type, name, permissions) + SELECT 'user', {{ q(listmonk_api_role) }}, ARRAY['subscribers:get', 'subscribers:manage'] + WHERE NOT EXISTS (SELECT 1 FROM roles WHERE type = 'user' AND name = {{ q(listmonk_api_role) }}) + RETURNING id +) +SELECT count(*) FROM ins; +WITH ins AS ( + INSERT INTO roles (type, name) + SELECT 'list', {{ q(listmonk_api_role) }} + WHERE NOT EXISTS (SELECT 1 FROM roles WHERE type = 'list' AND name = {{ q(listmonk_api_role) }} AND parent_id IS NULL) + RETURNING id +) +SELECT count(*) FROM ins; +WITH parent AS ( + SELECT id FROM roles WHERE type = 'list' AND name = {{ q(listmonk_api_role) }} AND parent_id IS NULL +), ins AS ( + INSERT INTO roles (type, parent_id, list_id, permissions) + SELECT 'list', parent.id, l.id, ARRAY['list:get', 'list:manage'] + FROM parent, lists l + WHERE l.name IN ({% for n in listmonk_api_lists %}{{ q(n) }}{{ ',' if not loop.last }}{% endfor %}) + ON CONFLICT (parent_id, list_id) DO NOTHING + RETURNING id +) +SELECT count(*) FROM ins; +WITH ur AS ( + SELECT id FROM roles WHERE type = 'user' AND name = {{ q(listmonk_api_role) }} +), lr AS ( + SELECT id FROM roles WHERE type = 'list' AND name = {{ q(listmonk_api_role) }} AND parent_id IS NULL +), up AS ( + INSERT INTO users (username, password_login, password, email, name, type, user_role_id, list_role_id, status) + SELECT {{ q(listmonk_api_user) }}, false, {{ q(listmonk_api_token | hash('sha256')) }}, + {{ q(listmonk_api_user ~ '@api') }}, {{ q(listmonk_api_user) }}, 'api', ur.id, lr.id, 'enabled' + FROM ur, lr + ON CONFLICT (username) DO UPDATE + SET password = EXCLUDED.password, user_role_id = EXCLUDED.user_role_id, + list_role_id = EXCLUDED.list_role_id, status = 'enabled', updated_at = NOW() + WHERE (users.password, users.user_role_id, users.list_role_id, users.status) + IS DISTINCT FROM (EXCLUDED.password, EXCLUDED.user_role_id, EXCLUDED.list_role_id, 'enabled'::user_status) + RETURNING id +) +SELECT count(*) FROM up; +{% endif %} + {% if listmonk_remove_sample_data | bool %} WITH del AS ( DELETE FROM campaigns diff --git a/tests/render.yml b/tests/render.yml index 950dc17..884e181 100644 --- a/tests/render.yml +++ b/tests/render.yml @@ -17,6 +17,7 @@ listmonk_admin_password: render-only listmonk_smtp_password: render-only listmonk_oidc_client_secret: render-only + listmonk_api_token: render-only-api-token - name: Output directory ansible.builtin.file: path: "{{ out }}"