decidim: mirror koopa-decidim setup (9027, docs for /sm showcase)
This commit is contained in:
parent
d519fb3c80
commit
9ae0dfa429
21 changed files with 998 additions and 2 deletions
247
configs/decidim/seed_example.rb
Normal file
247
configs/decidim/seed_example.rb
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
# frozen_string_literal: true
|
||||
# Public process + meeting + page + proposal (readable without an account).
|
||||
# Optional participant from env: DECIDIM_ISOC_EMAIL / DECIDIM_ISOC_PASSWORD.
|
||||
# Optional avatar: DECIDIM_ISOC_AVATAR (PNG path inside the container).
|
||||
# Usage: bundle exec rails runner /code/lib/koopa_seed_example.rb
|
||||
|
||||
def t(en, de, fr)
|
||||
{ "en" => en, "de" => de, "fr" => fr }
|
||||
end
|
||||
|
||||
def fail_model!(record)
|
||||
raise "#{record.class}: #{record.errors.full_messages.join("; ")}"
|
||||
end
|
||||
|
||||
host = ENV.fetch("DECIDIM_HOST", "decidim.hacktivism.ch")
|
||||
org = Decidim::Organization.find_by!(host: host)
|
||||
admin = Decidim::User.find_by!(email: ENV.fetch("DECIDIM_ADMIN_EMAIL"), organization: org)
|
||||
|
||||
org.update!(force_users_to_authenticate_before_access_organization: false)
|
||||
|
||||
slug = "example"
|
||||
process = Decidim::ParticipatoryProcess.find_or_initialize_by(organization: org, slug: slug)
|
||||
process.assign_attributes(
|
||||
title: t(
|
||||
"Public digital commons",
|
||||
"Öffentliche digitale Allmende",
|
||||
"Communs numériques publics"
|
||||
),
|
||||
subtitle: t(
|
||||
"Which self-hosted services stay readable without an account?",
|
||||
"Welche selbst gehosteten Dienste bleiben ohne Konto lesbar?",
|
||||
"Quels services auto-hébergés restent lisibles sans compte ?"
|
||||
),
|
||||
short_description: t(
|
||||
"<p>hacktivism.ch already runs Lemmy, Bonfire, Castopod and Decidim. This process asks what must stay readable without a login — and what may require an account to write.</p>",
|
||||
"<p>hacktivism.ch betreibt bereits Lemmy, Bonfire, Castopod und Decidim. Dieser Prozess fragt, was ohne Anmeldung lesbar bleiben muss — und wofür ein Konto zum Schreiben reicht.</p>",
|
||||
"<p>hacktivism.ch fait déjà tourner Lemmy, Bonfire, Castopod et Decidim. Ce processus demande ce qui doit rester lisible sans compte — et ce qui peut exiger un compte pour écrire.</p>"
|
||||
),
|
||||
description: t(
|
||||
"<p>Reading civic material should not depend on creating an account. Writing (proposals, comments, votes) can.</p>" \
|
||||
"<p>Concrete questions:</p><ul>" \
|
||||
"<li>Process pages, meetings and last activities stay public.</li>" \
|
||||
"<li>No third-party trackers; cookies only what the software needs.</li>" \
|
||||
"<li>Which other stacks on hacktivism.ch (Lemmy feeds, Castopod episodes, Bonfire public posts) follow the same rule.</li>" \
|
||||
"</ul><p>Anyone can read this process. Sign in only to take part.</p>",
|
||||
"<p>Öffentliches Beteiligungsmaterial soll ohne Konto lesbar sein. Schreiben (Vorschläge, Kommentare, Stimmen) kann ein Konto brauchen.</p>" \
|
||||
"<p>Konkrete Fragen:</p><ul>" \
|
||||
"<li>Prozessseiten, Treffen und letzte Aktivitäten bleiben öffentlich.</li>" \
|
||||
"<li>Keine Drittanbieter-Tracker; Cookies nur, was die Software braucht.</li>" \
|
||||
"<li>Welche anderen Dienste auf hacktivism.ch (Lemmy, Castopod, Bonfire) derselben Regel folgen.</li>" \
|
||||
"</ul><p>Lesen ohne Anmeldung. Anmelden nur zum Mitmachen.</p>",
|
||||
"<p>Lire du matériel civique ne doit pas exiger un compte. Écrire (propositions, commentaires, votes) peut le faire.</p>" \
|
||||
"<p>Questions concrètes :</p><ul>" \
|
||||
"<li>Pages de processus, rencontres et dernières activités restent publiques.</li>" \
|
||||
"<li>Pas de traqueurs tiers ; cookies uniquement pour le logiciel.</li>" \
|
||||
"<li>Quels autres services sur hacktivism.ch (Lemmy, Castopod, Bonfire) suivent la même règle.</li>" \
|
||||
"</ul><p>Lecture sans compte. Connexion seulement pour participer.</p>"
|
||||
),
|
||||
start_date: Date.current,
|
||||
end_date: Date.current + 180,
|
||||
published_at: process.published_at || Time.current,
|
||||
private_space: false,
|
||||
promoted: true,
|
||||
scopes_enabled: false,
|
||||
weight: 1
|
||||
)
|
||||
process.save || fail_model!(process)
|
||||
puts "process id=#{process.id} slug=#{process.slug} published=#{process.published_at}"
|
||||
|
||||
step = process.steps.find_or_initialize_by(position: 1)
|
||||
step.assign_attributes(
|
||||
title: t("Open discussion", "Offene Diskussion", "Discussion ouverte"),
|
||||
description: t(
|
||||
"<p>Collect positions on public-by-default reading. No decision this step.</p>",
|
||||
"<p>Positionen zum Lesen ohne Konto sammeln. In dieser Phase keine Entscheidung.</p>",
|
||||
"<p>Recueillir les positions sur la lecture sans compte. Pas de décision à cette étape.</p>"
|
||||
),
|
||||
start_date: Date.current,
|
||||
end_date: Date.current + 180,
|
||||
active: true
|
||||
)
|
||||
step.save || fail_model!(step)
|
||||
puts "step id=#{step.id} active=#{step.active}"
|
||||
|
||||
pages = Decidim::Component.find_or_initialize_by(
|
||||
participatory_space: process,
|
||||
manifest_name: "pages"
|
||||
)
|
||||
pages.assign_attributes(
|
||||
name: t("Background", "Hintergrund", "Contexte"),
|
||||
published_at: pages.published_at || Time.current,
|
||||
visible: true,
|
||||
weight: 0
|
||||
)
|
||||
pages.save || fail_model!(pages)
|
||||
page = Decidim::Pages::Page.find_or_initialize_by(component: pages)
|
||||
page.body = t(
|
||||
"<p>ISOC-style public-interest internet: people should inspect how a civic tool works without first handing over an identity.</p>" \
|
||||
"<p>On this instance the stock cookie banner is hidden; only essential consent is set. The same idea applies to participation spaces: the text of a process is not a privilege of registered users.</p>" \
|
||||
"<p>This page is part of that argument. It is published on purpose.</p>",
|
||||
"<p>Internet im öffentlichen Interesse: man soll sehen können, wie ein Beteiligungswerkzeug arbeitet, ohne zuerst eine Identität abzugeben.</p>" \
|
||||
"<p>Auf dieser Instanz ist der Vorrats-Cookie-Banner ausgeblendet; es gilt nur essential. Dieselbe Idee gilt für Beteiligungsräume: der Text eines Prozesses ist kein Privileg registrierter Konten.</p>" \
|
||||
"<p>Diese Seite ist absichtlich öffentlich.</p>",
|
||||
"<p>Internet d'intérêt public : on doit pouvoir voir comment un outil civique fonctionne sans d'abord céder une identité.</p>" \
|
||||
"<p>Ici, la bannière cookies par défaut est masquée ; seul l'essentiel est posé. Même idée pour les espaces de participation : le texte d'un processus n'est pas un privilège des comptes.</p>" \
|
||||
"<p>Cette page est publique volontairement.</p>"
|
||||
)
|
||||
page.save || fail_model!(page)
|
||||
puts "pages component id=#{pages.id} page=#{page.id}"
|
||||
|
||||
meetings = Decidim::Component.find_or_initialize_by(
|
||||
participatory_space: process,
|
||||
manifest_name: "meetings"
|
||||
)
|
||||
meetings.assign_attributes(
|
||||
name: t("Meetings", "Treffen", "Rencontres"),
|
||||
published_at: meetings.published_at || Time.current,
|
||||
visible: true,
|
||||
weight: 1
|
||||
)
|
||||
meetings.save || fail_model!(meetings)
|
||||
puts "meetings component id=#{meetings.id} published=#{meetings.published_at}"
|
||||
|
||||
start_at = Time.current.utc.change(hour: 18, min: 0) + 7.days
|
||||
finish_at = start_at + 2.hours
|
||||
meeting = Decidim::Meetings::Meeting.find_or_initialize_by(
|
||||
component: meetings,
|
||||
author: admin
|
||||
)
|
||||
meeting.title = t(
|
||||
"Open session: reading without an account",
|
||||
"Offene Runde: Lesen ohne Konto",
|
||||
"Séance ouverte : lire sans compte"
|
||||
)
|
||||
meeting.assign_attributes(
|
||||
description: t(
|
||||
"<p>Public agenda (no registration to read):</p><ol>" \
|
||||
"<li>What is already public on hacktivism.ch (Lemmy, Decidim, Castopod).</li>" \
|
||||
"<li>Privacy by default — no tracker banner as a gate.</li>" \
|
||||
"<li>Where login is justified (writing, admin).</li>" \
|
||||
"</ol><p>Online / public. Signing in is optional.</p>",
|
||||
"<p>Öffentliche Traktanden (Lesen ohne Anmeldung):</p><ol>" \
|
||||
"<li>Was auf hacktivism.ch schon öffentlich ist (Lemmy, Decidim, Castopod).</li>" \
|
||||
"<li>Privatsphäre als Vorgabe — kein Tracker-Banner als Tür.</li>" \
|
||||
"<li>Wo ein Login gerechtfertigt ist (Schreiben, Admin).</li>" \
|
||||
"</ol><p>Online / öffentlich. Anmelden ist freiwillig.</p>",
|
||||
"<p>Ordre du jour public (lecture sans inscription) :</p><ol>" \
|
||||
"<li>Ce qui est déjà public sur hacktivism.ch (Lemmy, Decidim, Castopod).</li>" \
|
||||
"<li>Vie privée par défaut — pas de bannière traqueur comme porte.</li>" \
|
||||
"<li>Où un compte se justifie (écriture, admin).</li>" \
|
||||
"</ol><p>En ligne / public. Connexion facultative.</p>"
|
||||
),
|
||||
start_time: meeting.start_time || start_at,
|
||||
end_time: meeting.end_time || finish_at,
|
||||
address: "hacktivism.ch",
|
||||
location: t("Online, public stream notes", "Online, öffentliche Notizen", "En ligne, notes publiques"),
|
||||
location_hints: t(
|
||||
"No ticket. Page stays readable if you do not attend.",
|
||||
"Kein Ticket. Die Seite bleibt lesbar, wenn man nicht teilnimmt.",
|
||||
"Pas de billet. La page reste lisible sans y assister."
|
||||
),
|
||||
type_of_meeting: "in_person",
|
||||
registration_type: "registration_disabled",
|
||||
registrations_enabled: false,
|
||||
private_meeting: false,
|
||||
transparent: true,
|
||||
published_at: meeting.published_at || Time.current,
|
||||
comments_enabled: true
|
||||
)
|
||||
meeting.save || fail_model!(meeting)
|
||||
puts "meeting id=#{meeting.id} published=#{meeting.published_at} start=#{meeting.start_time}"
|
||||
|
||||
proposals = Decidim::Component.find_or_initialize_by(
|
||||
participatory_space: process,
|
||||
manifest_name: "proposals"
|
||||
)
|
||||
proposals.assign_attributes(
|
||||
name: t("Proposals", "Vorschläge", "Propositions"),
|
||||
published_at: proposals.published_at || Time.current,
|
||||
visible: true,
|
||||
weight: 2
|
||||
)
|
||||
proposals.save || fail_model!(proposals)
|
||||
|
||||
proposal = Decidim::Proposals::Proposal.find_or_initialize_by(component: proposals)
|
||||
if proposal.new_record? || proposal.title.blank?
|
||||
proposal.title = t(
|
||||
"Keep public processes readable without login",
|
||||
"Öffentliche Prozesse ohne Anmeldung lesbar halten",
|
||||
"Garder les processus publics lisibles sans connexion"
|
||||
)
|
||||
proposal.body = t(
|
||||
"<p>Decide as a standing rule: published process pages, meetings and this proposal list stay readable without an account. Login is only for creating or endorsing proposals, commenting, and administration.</p>",
|
||||
"<p>Als Regel festhalten: veröffentlichte Prozessseiten, Treffen und diese Vorschlagsliste bleiben ohne Konto lesbar. Login nur zum Erstellen oder Unterstützen von Vorschlägen, Kommentieren und für die Administration.</p>",
|
||||
"<p>En faire une règle : les pages de processus publiées, les rencontres et cette liste de propositions restent lisibles sans compte. Connexion seulement pour créer ou soutenir, commenter, et pour l'administration.</p>"
|
||||
)
|
||||
proposal.add_coauthor(admin)
|
||||
proposal.published_at = Time.current
|
||||
end
|
||||
proposal.save || fail_model!(proposal)
|
||||
puts "proposal id=#{proposal.id} published=#{proposal.published_at}"
|
||||
|
||||
block = Decidim::ContentBlock.find_or_initialize_by(
|
||||
organization: org,
|
||||
scope_name: "homepage",
|
||||
manifest_name: "highlighted_processes"
|
||||
)
|
||||
block.weight ||= 45
|
||||
block.published_at ||= Time.current
|
||||
block.save || fail_model!(block)
|
||||
puts "homepage highlighted_processes published=#{block.published_at}"
|
||||
|
||||
isoc_email = ENV["DECIDIM_ISOC_EMAIL"].to_s.strip
|
||||
isoc_password = ENV["DECIDIM_ISOC_PASSWORD"].to_s
|
||||
if isoc_email.empty? || isoc_password.empty?
|
||||
puts "isoc user skipped (DECIDIM_ISOC_EMAIL / DECIDIM_ISOC_PASSWORD unset)"
|
||||
else
|
||||
isoc = Decidim::User.find_or_initialize_by(email: isoc_email, organization: org)
|
||||
isoc.name = ENV.fetch("DECIDIM_ISOC_NAME", "isoc")
|
||||
isoc.nickname = ENV.fetch("DECIDIM_ISOC_NICKNAME", "isoc")
|
||||
isoc.password = isoc_password
|
||||
isoc.password_confirmation = isoc_password
|
||||
isoc.admin = false
|
||||
isoc.confirmed_at ||= Time.current
|
||||
isoc.tos_agreement = true
|
||||
isoc.accepted_tos_version = org.tos_version
|
||||
isoc.locale = org.default_locale
|
||||
isoc.save || fail_model!(isoc)
|
||||
avatar_path = ENV["DECIDIM_ISOC_AVATAR"].to_s
|
||||
if !avatar_path.empty? && File.file?(avatar_path)
|
||||
isoc.avatar.purge if isoc.avatar.attached?
|
||||
isoc.avatar.attach(
|
||||
io: File.open(avatar_path, "rb"),
|
||||
filename: "isoc-gold.png",
|
||||
content_type: "image/png"
|
||||
)
|
||||
isoc.save || fail_model!(isoc)
|
||||
puts "isoc avatar attached from #{avatar_path} blob=#{isoc.avatar.blob&.byte_size}"
|
||||
else
|
||||
puts "isoc avatar skipped (DECIDIM_ISOC_AVATAR missing)"
|
||||
end
|
||||
puts "isoc user id=#{isoc.id} email=#{isoc.email} nick=#{isoc.nickname} admin=#{isoc.admin} confirmed=#{isoc.confirmed_at}"
|
||||
end
|
||||
|
||||
puts "public process: https://#{host}/processes/#{process.slug}"
|
||||
puts "public meeting: https://#{host}/processes/#{process.slug}/f/#{meetings.id}/meetings/#{meeting.id}"
|
||||
puts "public proposal: https://#{host}/processes/#{process.slug}/f/#{proposals.id}/proposals/#{proposal.id}"
|
||||
Loading…
Add table
Add a link
Reference in a new issue