decidim-cdtb 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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