decidim-cdtb 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Oliver Valls
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # Decidim::Cdtb
2
+
3
+ This is CodiTramuntana's Decidim Toolbelt (cdtb), a gem to help managing Decidim applications.
4
+
5
+
6
+ ## Installation
7
+
8
+ Install the gem and add to the application's Gemfile by executing:
9
+
10
+ $ bundle add decidim-cdtb
11
+
12
+ If bundler is not being used to manage dependencies, install the gem by executing:
13
+
14
+ $ gem install decidim-cdtb
15
+
16
+ ## Usage
17
+
18
+ ### Rake tasks
19
+
20
+ #### Organizations information
21
+
22
+ Returns information regarding the organizations in a multitenant installation that match a search term ignorecase.
23
+
24
+
25
+ The following will return all the attributes for all organizations that contain the "vila" term in its host name:
26
+
27
+ ```
28
+ bin/rake cdtb:org_by_host_like[vila,true]
29
+ ```
30
+
31
+ With the `full` argument set to `true` will return the most relevant attributes:
32
+
33
+ ```
34
+ bin/rake cdtb:org_by_host_like[vila]
35
+ >>> Organization [1] Sant Boi de Llobregat:
36
+ host: localhost, time_zone: Madrid, locales: ca + [ca, es, oc], available authorizations: [postal_letter, members_picker_authorization_handler]
37
+ ```
38
+
39
+ #### Fix nicknames
40
+
41
+ In a previous version than Decidim v0.25 a validation to the `Decidim::User.nickname` was added with a migration to fix existing nicknames. But the migration was only taking into acocunt managed (impersonated) users.
42
+
43
+ This task iterates (with `find_each`) over all non managed users and nicknamizes the nickname.
44
+
45
+ To execute the task run:
46
+
47
+ ```
48
+ bin/rake cdtb:fix_nicknames
49
+ ```
50
+
51
+ #### Anonymize production dump
52
+
53
+ Anonymize rake task was taken from https://github.com/AjuntamentdeBarcelona/decidim-barcelona
54
+
55
+ Available rake tasks:
56
+
57
+ ```bin/rake cdtb:anonymize:check``` allows you to check if you can anonymize production dump
58
+
59
+ ```bin/rake cdtb:anonymize:all``` anonymizes whole production dump (without proposals)
60
+
61
+ ```bin/rake cdtb:anonymize:users``` anonymizes users
62
+
63
+ ```bin/rake cdtb:anonymize:proposals``` anonymizes proposals
64
+
65
+ ```bin/rake cdtb:anonymize:user_groups``` anonymizes user groups
66
+
67
+ ```bin/rake cdtb:anonymize:system_admins``` anonymizes system admins
68
+
69
+ ```bin/rake cdtb:anonymize:paper_trail``` anonymizes paper trails
70
+
71
+ #### Migrate ActiveStorage service from S3 to local
72
+
73
+ To migrate from S3 to local storage, the identified steps will be:
74
+
75
+ 1. Download the assets to a temporary directory:
76
+ `aws s3 sync s3://bucket-name tmp/storage/`
77
+ 2. Move the downloaded assets into the local storage directory doing the sharding:
78
+ `bin/rake cdtb:s3_to_local:do_sharding`
79
+ 3. Update all blobs to use the local service
80
+ `bin/rake cdtb:s3_to_local:set_local_service_on_blobs`
81
+ 4. Clean the cache:
82
+ `bin/rake cache:clear`
83
+ 5. Restart the Rails server
84
+
85
+ ## Development
86
+
87
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
88
+
89
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
90
+
91
+ ## Run tests
92
+
93
+ Node 16.9.1 is required!
94
+
95
+ Create a dummy app:
96
+
97
+ ```bash
98
+ bin/rails decidim:generate_external_test_app
99
+ ```
100
+
101
+ And run tests:
102
+
103
+ ```bash
104
+ bundle exec rspec spec
105
+ ```
106
+
107
+
108
+ ## Contributing
109
+
110
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/decidim-cdtb. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/decidim-cdtb/blob/master/CODE_OF_CONDUCT.md).
111
+
112
+ ## License
113
+
114
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
115
+
116
+ ## Code of Conduct
117
+
118
+ Everyone interacting in the Decidim::Cdtb project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/decidim-cdtb/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "decidim/dev/common_rake"
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ require "rubocop/rake_task"
10
+
11
+ RuboCop::RakeTask.new
12
+
13
+ task default: %i[spec rubocop]
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ # Base Job for all Cdtb jobs
5
+ class ApplicationJob < ActiveJob::Base
6
+ # Automatically retry jobs that encountered a deadlock
7
+ retry_on ActiveRecord::Deadlocked
8
+
9
+ # Most jobs are safe to ignore if the underlying records are no longer available
10
+ discard_on ActiveJob::DeserializationError
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cdtb
4
+ # Fixes the nickname of the Decidim::User with the given `user_id`.
5
+ class FixNicknameJob < ApplicationJob
6
+ queue_as :default
7
+
8
+ def perform(user_id)
9
+ user= Decidim::User.find(user_id)
10
+ previous= user.nickname
11
+
12
+ nickname = Decidim::User.nicknamize(previous, organization: user.organization)
13
+ user.update_attribute(:nickname, nickname)
14
+
15
+ Rails.logger.info "#{user.id}-#{user.email}: #{previous} => #{user.nickname}"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/decidim/cdtb/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "decidim-cdtb"
7
+ spec.version = Decidim::Cdtb::VERSION
8
+ spec.authors = ["Oliver Valls"]
9
+ spec.email = ["199462+tramuntanal@users.noreply.github.com"]
10
+
11
+ spec.summary = "CodiTramuntana's Decidim Toolbelt (cdtb)."
12
+ spec.description = "A gem to help managing Decidim applications."
13
+ spec.homepage = "http://github.com/CodiTramunana/cdtb"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.7.5"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
26
+ end
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency "decidim", Decidim::Cdtb::DECIDIM_MIN_VERSION
33
+ spec.add_dependency "rails", ">= 6"
34
+ spec.add_dependency "ruby-progressbar"
35
+
36
+ spec.add_development_dependency "decidim-dev", Decidim::Cdtb::DECIDIM_MIN_VERSION
37
+ spec.add_development_dependency "faker"
38
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Cdtb
5
+ # Census have no public app (see AdminEngine)
6
+ class Engine < ::Rails::Engine
7
+ isolate_namespace Decidim::Cdtb
8
+
9
+ initializer "psych.tmp.fix" do |_app|
10
+ # Workaround for https://stackoverflow.com/questions/72970170/upgrading-to-rails-6-1-6-1-causes-psychdisallowedclass-tried-to-load-unspecif
11
+ Rails.application.config.active_record.use_yaml_unsafe_load = true
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Cdtb
5
+ module Fixes
6
+ # Fixes invalid Decidim::User#nickname
7
+ #
8
+ class NicknameFixer < ::Decidim::Cdtb::Task
9
+ def initialize
10
+ progress_bar= { title: "Decidim::User" }
11
+ super("FIX NICKNAMES", progress_bar: progress_bar)
12
+ end
13
+
14
+ def prepare_execution(_ctx)
15
+ @num_users= Decidim::User.count
16
+ log_task_info("Checking #{@num_users} users...")
17
+ end
18
+
19
+ def total_items
20
+ @num_users
21
+ end
22
+
23
+ def do_execution(context)
24
+ progress_bar= context[:progress_bar]
25
+
26
+ Decidim::User.find_each do |user|
27
+ Decidim::User.validators_on(:nickname).each do |validator|
28
+ validator.validate_each(user, :nickname, user.nickname)
29
+ end
30
+
31
+ if user.errors[:nickname].any?
32
+ ::Cdtb::FixNicknameJob.perform_later(user.id)
33
+ @num_applied+= 1
34
+ end
35
+ progress_bar.increment
36
+ end
37
+ end
38
+
39
+ def end_execution(_ctx)
40
+ log_task_step("#{@num_applied} users nicknamized")
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Cdtb
5
+ module Multitenants
6
+ # Finds information about the Organization, or Organizations, searching by the :host_term argument ignorecase.
7
+ # Set :full (second param) to `true` for full information
8
+ #
9
+ class OrgByHostLike < ::Decidim::Cdtb::Task
10
+ def initialize(host_term, full_info)
11
+ @host_term= host_term
12
+ @show_full_info= full_info == "true"
13
+ super("ORG BY HOST LIKE")
14
+ end
15
+
16
+ def prepare_execution(_ctx)
17
+ @query = Decidim::Organization.where("host ilike ?", "%#{@host_term}%")
18
+ log_task_info("Found #{@query.count} organizations")
19
+ end
20
+
21
+ def do_execution(_ctx)
22
+ @query.find_each do |org|
23
+ log_task_step("Organization [#{org.id}] #{org.name}:")
24
+ if show_full_info?
25
+ show_full_info(org)
26
+ else
27
+ h= {
28
+ host: org.host,
29
+ time_zone: org.time_zone,
30
+ locales: "#{org.default_locale} + [#{org.available_locales&.join(", ")}]",
31
+ available_authorizations: org.available_authorizations&.join(", ")
32
+ }
33
+ do_log(h.to_yaml)
34
+ end
35
+ do_log("---------------------------------------------------------")
36
+ end
37
+ end
38
+
39
+ #----------------------------------------------------------------
40
+
41
+ private
42
+
43
+ #----------------------------------------------------------------
44
+
45
+ def show_full_info?
46
+ @show_full_info
47
+ end
48
+
49
+ def show_full_info(org)
50
+ do_log(org.attributes.to_yaml)
51
+ end
52
+
53
+ def show_summary_info(org)
54
+ h= {
55
+ host: org.host,
56
+ time_zone: org.time_zone,
57
+ locales: "#{org.default_locale} + [#{org.available_locales&.join(", ")}]",
58
+ available_authorizations: org.available_authorizations&.join(", ")
59
+ }
60
+ do_log(h.to_yaml)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Cdtb
5
+ module Storage
6
+ # Given that all assets has already been copied from S3 to storage/,
7
+ # this task performs the sharding of the downloaded files.
8
+ #
9
+ # This step is required because in S3 all assets are stored flat at the same level (directory),
10
+ # but local service stores the files with sharding.
11
+ class LocalSharding < ::Decidim::Cdtb::Task
12
+ def initialize
13
+ progress_bar= { title: "ActiveStorage::Blob" }
14
+ super("S3 to local: DO SHARDING", progress_bar: progress_bar)
15
+ end
16
+
17
+ def prepare_execution(_ctx)
18
+ @num_blobs= ActiveStorage::Blob.count
19
+ log_task_info("Checking #{@num_blobs} blobs...")
20
+ end
21
+
22
+ def total_items
23
+ @num_blobs
24
+ end
25
+
26
+ def do_execution(context)
27
+ progress_bar= context[:progress_bar]
28
+
29
+ ActiveStorage::Blob.find_each do |blob|
30
+ path= ActiveStorage::Blob.service.path_for(blob.key)
31
+ src_file= Rails.root.join("tmp/storage", blob.key)
32
+ if File.exist?(src_file)
33
+ shard_asset(blob, path)
34
+ @num_applied+= 1
35
+ else
36
+ logger.warn "File Not Found or directory: #{path}"
37
+ end
38
+ progress_bar.increment
39
+ end
40
+ end
41
+
42
+ def end_execution(_ctx)
43
+ log_task_info("#{@num_applied} blobs sharded")
44
+ end
45
+
46
+ #----------------------------------------------------------------
47
+
48
+ private
49
+
50
+ #----------------------------------------------------------------
51
+
52
+ def shard_asset(blob, path)
53
+ blob_dir= File.dirname path
54
+ logger.info "Creating dir: #{blob_dir}"
55
+ FileUtils.mkdir_p(blob_dir)
56
+ FileUtils.mv Rails.root.join("tmp/storage", blob.key), path, force: true
57
+ logger.info "Sharding for file: #{path}"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Cdtb
5
+ module Storage
6
+ # Updates all ActiveStorage::Blob rows in the DB to use the :local service.
7
+ class SetLocalOnBlobs < ::Decidim::Cdtb::Task
8
+ def initialize
9
+ super("S3 to local: FORCE LOCAL SERVICE")
10
+ end
11
+
12
+ def prepare_execution(_ctx)
13
+ @num_blobs= ActiveStorage::Blob.count
14
+ log_task_info("Updating #{@num_blobs} blobs...")
15
+ end
16
+
17
+ def total_items
18
+ @num_blobs
19
+ end
20
+
21
+ def do_execution(_context)
22
+ ActiveStorage::Blob.update(service_name: "local")
23
+ end
24
+
25
+ def end_execution(_ctx)
26
+ log_task_info("Blobs updated")
27
+ end
28
+
29
+ #----------------------------------------------------------------
30
+
31
+ #----------------------------------------------------------------
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "decidim/cdtb/tasks_utils"
4
+
5
+ module Decidim
6
+ module Cdtb
7
+ # Parent class with common behaviour for all tasks.
8
+ #
9
+ class Task
10
+ include Decidim::Cdtb::TasksUtils
11
+
12
+ # title: The title shown at the begining of the Task
13
+ # progress_bar: A hash with one key: :title for the title of the ProgressBar.
14
+ def initialize(title, progress_bar: nil)
15
+ @title= title
16
+ @progress_bar= progress_bar
17
+ @num_applied = 0
18
+ end
19
+
20
+ attr_reader :num_applied, :title
21
+
22
+ def init
23
+ log_task_title(@title)
24
+ @start_time= Time.zone.now
25
+ do_log("▶️ Starting at #{@start_time}")
26
+ end
27
+
28
+ def execute!
29
+ init
30
+ ctx= {}
31
+ ctx[:progress_bar]= ProgressBar.create(total: total_items, title: title) if has_progress?
32
+ prepare_execution(ctx)
33
+ do_execution(ctx)
34
+ end_execution(ctx)
35
+ finish
36
+ end
37
+
38
+ def finish
39
+ do_log("⏱️ Took #{Time.zone.now - @start_time} seconds")
40
+ log_task_end
41
+ end
42
+
43
+ #################################
44
+
45
+ protected
46
+
47
+ #################################
48
+
49
+ # May be used by subclasses for preparing before executing the task
50
+ def prepare_execution(context); end
51
+
52
+ # Subclasses must implement the steps of the task overriding this method.
53
+ def do_execution(context); end
54
+
55
+ # May be used by subclasses for doing whatever after executing the task
56
+ def end_execution(context); end
57
+
58
+ def has_progress?
59
+ @progress_bar.present?
60
+ end
61
+
62
+ # The number of items to be processed.
63
+ # Required by the progress bar.
64
+ def total_items; end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "decidim/cdtb/tasks_utils"
4
+ require "decidim/cdtb/task"
5
+ require "decidim/cdtb/fixes/nickname_fixer"
6
+ require "decidim/cdtb/multitenants/org_by_host_like"
7
+ require "decidim/cdtb/storage/local_sharding"
8
+ require "decidim/cdtb/storage/set_local_on_blobs"
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-progressbar"
4
+
5
+ module Decidim
6
+ module Cdtb
7
+ # Reusable utils for Cdtb Rake tasks.
8
+ module TasksUtils
9
+ def logger
10
+ Rails.logger
11
+ end
12
+
13
+ def do_log(msg)
14
+ puts msg
15
+ logger.info(msg)
16
+ end
17
+
18
+ def log_task_title(title)
19
+ do_log("⚙️ #{title}")
20
+ end
21
+
22
+ def log_task_step(description)
23
+ do_log("➡️ #{description}")
24
+ end
25
+
26
+ def log_task_info(info)
27
+ do_log("ℹ️ #{info}")
28
+ end
29
+
30
+ def log_task_end
31
+ end_comment= "✅ Done."
32
+ do_log(end_comment)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Cdtb
5
+ VERSION = "0.1.2"
6
+ DECIDIM_MIN_VERSION = ">= 0.26.2"
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "cdtb/version"
4
+ require_relative "cdtb/engine"
5
+
6
+ module Decidim
7
+ module Cdtb
8
+ class Error < StandardError; end
9
+ end
10
+ end