# 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( "

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.

", "

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.

", "

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.

" ), description: t( "

Reading civic material should not depend on creating an account. Writing (proposals, comments, votes) can.

" \ "

Concrete questions:

Anyone can read this process. Sign in only to take part.

", "

Öffentliches Beteiligungsmaterial soll ohne Konto lesbar sein. Schreiben (Vorschläge, Kommentare, Stimmen) kann ein Konto brauchen.

" \ "

Konkrete Fragen:

Lesen ohne Anmeldung. Anmelden nur zum Mitmachen.

", "

Lire du matériel civique ne doit pas exiger un compte. Écrire (propositions, commentaires, votes) peut le faire.

" \ "

Questions concrètes :

Lecture sans compte. Connexion seulement pour participer.

" ), 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( "

Collect positions on public-by-default reading. No decision this step.

", "

Positionen zum Lesen ohne Konto sammeln. In dieser Phase keine Entscheidung.

", "

Recueillir les positions sur la lecture sans compte. Pas de décision à cette étape.

" ), 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( "

ISOC-style public-interest internet: people should inspect how a civic tool works without first handing over an identity.

" \ "

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.

" \ "

This page is part of that argument. It is published on purpose.

", "

Internet im öffentlichen Interesse: man soll sehen können, wie ein Beteiligungswerkzeug arbeitet, ohne zuerst eine Identität abzugeben.

" \ "

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.

" \ "

Diese Seite ist absichtlich öffentlich.

", "

Internet d'intérêt public : on doit pouvoir voir comment un outil civique fonctionne sans d'abord céder une identité.

" \ "

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.

" \ "

Cette page est publique volontairement.

" ) 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( "

Public agenda (no registration to read):

    " \ "
  1. What is already public on hacktivism.ch (Lemmy, Decidim, Castopod).
  2. " \ "
  3. Privacy by default — no tracker banner as a gate.
  4. " \ "
  5. Where login is justified (writing, admin).
  6. " \ "

Online / public. Signing in is optional.

", "

Öffentliche Traktanden (Lesen ohne Anmeldung):

    " \ "
  1. Was auf hacktivism.ch schon öffentlich ist (Lemmy, Decidim, Castopod).
  2. " \ "
  3. Privatsphäre als Vorgabe — kein Tracker-Banner als Tür.
  4. " \ "
  5. Wo ein Login gerechtfertigt ist (Schreiben, Admin).
  6. " \ "

Online / öffentlich. Anmelden ist freiwillig.

", "

Ordre du jour public (lecture sans inscription) :

    " \ "
  1. Ce qui est déjà public sur hacktivism.ch (Lemmy, Decidim, Castopod).
  2. " \ "
  3. Vie privée par défaut — pas de bannière traqueur comme porte.
  4. " \ "
  5. Où un compte se justifie (écriture, admin).
  6. " \ "

En ligne / public. Connexion facultative.

" ), 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( "

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.

", "

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.

", "

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.

" ) 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}"