discourse_dev 0.0.4 → 0.1.0

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/auth/app/views/fake_discourse_connect/form.html.erb +120 -0
  3. data/auth/plugin.rb +220 -0
  4. data/avatars/03F55412-DE8A-4F83-AAA6-D67EE5CE48DA-200w.jpeg +0 -0
  5. data/avatars/1C4EEDC2-FE9C-40B3-A2C9-A038873EE692-200w.jpeg +0 -0
  6. data/avatars/26CFEFB3-21C8-49FC-8C19-8E6A62B6D2E0-200w.jpeg +0 -0
  7. data/avatars/282A12CA-E0D7-4011-8BDD-1FAFAAB035F7-200w.jpeg +0 -0
  8. data/avatars/2DDDE973-40EC-4004-ABC0-73FD4CD6D042-200w.jpeg +0 -0
  9. data/avatars/344CFC24-61FB-426C-B3D1-CAD5BCBD3209-200w.jpeg +0 -0
  10. data/avatars/852EC6E1-347C-4187-9D42-DF264CCF17BF-200w.jpeg +0 -0
  11. data/avatars/A7299C8E-CEFC-47D9-939A-3C8CA0EA4D13-200w.jpeg +0 -0
  12. data/avatars/AEF44435-B547-4B84-A2AE-887DFAEE6DDF-200w.jpeg +0 -0
  13. data/avatars/B3CF5288-34B0-4A5E-9877-5965522529D6-200w.jpeg +0 -0
  14. data/avatars/BA0CB1F2-8C79-4376-B13B-DD5FB8772537-200w.jpeg +0 -0
  15. data/avatars/E0B4CAB3-F491-4322-BEF2-208B46748D4A-200w.jpeg +0 -0
  16. data/avatars/FBEBF655-4886-455A-A4A4-D62B77DD419B-200w.jpeg +0 -0
  17. data/config/dev.yml +11 -0
  18. data/config/locales/client.en.yml +6 -0
  19. data/{lib/faker/locales/en.yml → config/locales/faker.en.yml} +1 -1
  20. data/config/routes.rb +5 -0
  21. data/lib/discourse_dev.rb +47 -4
  22. data/lib/discourse_dev/config.rb +95 -16
  23. data/lib/discourse_dev/engine.rb +5 -0
  24. data/lib/discourse_dev/group.rb +2 -1
  25. data/lib/discourse_dev/post.rb +104 -0
  26. data/lib/discourse_dev/record.rb +17 -0
  27. data/lib/discourse_dev/tasks/dev.rake +18 -1
  28. data/lib/discourse_dev/tasks/populate.rake +5 -0
  29. data/lib/discourse_dev/topic.rb +39 -3
  30. data/lib/discourse_dev/user.rb +51 -1
  31. data/lib/discourse_dev/version.rb +1 -1
  32. metadata +23 -4
  33. data/lib/discourse_dev/config.yml +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b40ff76d084fee2f0eeef50807149a399e6c4bbdf256259dd23a6925d6bdeb7
4
- data.tar.gz: 748af95af548a6d79ee21d96dd68688d5c554cb78391a416b63980805d4b25a3
3
+ metadata.gz: ee90422d22b330921c72be89d86be91edf94966537d869c06efb2687762853f4
4
+ data.tar.gz: cb5b3d27e5a93011c2ef10ff77d73b5393ed9a1f04a7efb538ef2f6a67776e12
5
5
  SHA512:
6
- metadata.gz: '0914512344fa239f36c6ae42c4890ef4cad9fd991a234c5f8b301751a84a89633d20f035e64e526935031767842b27e6a36758e8ea982a3672e83e22c2826b4b'
7
- data.tar.gz: d9cfa147a8616124232baef18d01203cd09b6c88a5d5c4c7b9a7527e4ef56d4f79c07c0d2ea13809907ba81b3cc38c4b669307d2a6ebef57b4d21364b96ba1b4
6
+ metadata.gz: 396e17d2e66c01d8a8e75381bd3a36264ddfc7d01ffc1dc3a578ee938a328805b35eda592d603388d38cba278a9c089a7a71a2ea03ba67a5bc3c30130b94a37a
7
+ data.tar.gz: b8fe74f3a03140cad606e9479b043e30b23c7eaf9b1f098e06613ad7c8662b3c742aab901e03965e64c0dfa0110b6c7cfa7a2a77d97e8e97b1cda63ac4c6e0db
@@ -0,0 +1,120 @@
1
+ <%# Layout/CSS borrowed from Omniauth's Form system %>
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6
+ <title>Fake DiscourseConnect Provider</title>
7
+ <style type='text/css'>
8
+ body {
9
+ background: #ccc;
10
+ font-family: "Lucida Grande", "Lucida Sans", Helvetica, Arial, sans-serif;
11
+ }
12
+
13
+ h1 {
14
+ text-align: center;
15
+ margin: 30px auto 0px;
16
+ font-size: 18px;
17
+ padding: 10px 10px 15px;
18
+ background: #555;
19
+ color: white;
20
+ width: 320px;
21
+ border: 10px solid #444;
22
+ border-bottom: 0;
23
+ -moz-border-radius-topleft: 10px;
24
+ -moz-border-radius-topright: 10px;
25
+ -webkit-border-top-left-radius: 10px;
26
+ -webkit-border-top-right-radius: 10px;
27
+ border-top-left-radius: 10px;
28
+ border-top-right-radius: 10px;
29
+ }
30
+
31
+ h1,
32
+ form {
33
+ -moz-box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.3);
34
+ -webkit-box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.3);
35
+ }
36
+
37
+ form {
38
+ background: white;
39
+ border: 10px solid #eee;
40
+ border-top: 0;
41
+ padding: 20px;
42
+ margin: 0px auto 40px;
43
+ width: 300px;
44
+ -moz-border-radius-bottomleft: 10px;
45
+ -moz-border-radius-bottomright: 10px;
46
+ -webkit-border-bottom-left-radius: 10px;
47
+ -webkit-border-bottom-right-radius: 10px;
48
+ border-bottom-left-radius: 10px;
49
+ border-bottom-right-radius: 10px;
50
+ }
51
+
52
+ label {
53
+ display: block;
54
+ font-weight: bold;
55
+ margin-bottom: 5px;
56
+ }
57
+
58
+ input, select {
59
+ font-size: 18px;
60
+ padding: 4px 8px;
61
+ display: block;
62
+ margin-bottom: 10px;
63
+ width: 280px;
64
+ }
65
+
66
+ select {
67
+ width: calc(280px + 20px);
68
+ }
69
+
70
+ button {
71
+ font-size: 22px;
72
+ padding: 4px 8px;
73
+ display: block;
74
+ margin: 20px auto 0;
75
+ }
76
+
77
+ fieldset {
78
+ border: 1px solid #ccc;
79
+ border-left: 0;
80
+ border-right: 0;
81
+ padding: 10px 0;
82
+ }
83
+
84
+ fieldset input {
85
+ width: 260px;
86
+ font-size: 16px;
87
+ }
88
+
89
+ details summary {
90
+ cursor: pointer;
91
+ margin-bottom: 10px;
92
+ }
93
+ </style>
94
+ </head>
95
+ <body>
96
+ <h1>Fake DiscourseConnect Provider</h1>
97
+ <form method='post' noValidate='noValidate'>
98
+ <input type='hidden' name='sso_payload' value='<%= @payload %>'/>
99
+ <% @simple_fields.each do |f| %>
100
+ <label for='<%= f %>'><%= f %>:</label><input type='text' id='<%= f %>' name='<%= f %>' value='<%= @defaults[f] %>'/>
101
+ <% end %>
102
+ <details>
103
+ <summary>Advanced</summary>
104
+ <% @advanced_fields.each do |f| %>
105
+ <% if @bools.include? f %>
106
+ <label for='<%= f %>'><%= f %>:</label>
107
+ <select name="<%= f %>" id="<%= f %>">
108
+ <% ["", "true", "false"].each do |opt| %>
109
+ <option <%= "selected" if @defaults[f] == opt %> value="<%= opt %>"><%= opt %></option>
110
+ <% end %>
111
+ </select>
112
+ <% else %>
113
+ <label for='<%= f %>'><%= f %>:</label><input type='text' id='<%= f %>' name='<%= f %>' value='<%= @defaults[f] %>'/>
114
+ <% end %>
115
+ <% end %>
116
+ </details>
117
+ <button type='submit'>Go</button>
118
+ </form>
119
+ </body>
120
+ </html>
data/auth/plugin.rb ADDED
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ # name: discourse-development-auth
4
+ # about: A fake authentication provider for development puposes only
5
+ # version: 1.0
6
+ # authors: David Taylor
7
+ # url: https://github.com/discourse/discourse-development-auth
8
+
9
+ raise "discourse-development-auth is highly insecure and should not be installed in production" if Rails.env.production?
10
+
11
+ PLUGIN_NAME = "discourse-development-auth"
12
+
13
+ module ::OmniAuth
14
+ module Strategies
15
+ class Development
16
+ include ::OmniAuth::Strategy
17
+
18
+ FIELDS = %w{
19
+ uid
20
+ name
21
+ email
22
+ email_verified
23
+ nickname
24
+ first_name
25
+ last_name
26
+ location
27
+ description
28
+ image
29
+ }
30
+
31
+ COOKIE = "development-auth-defaults"
32
+
33
+ def request_phase
34
+ return unless is_allowed?
35
+ if (env['REQUEST_METHOD'] == 'POST') && (request.params['uid'])
36
+ data = request.params.slice(*FIELDS)
37
+
38
+ r = Rack::Response.new
39
+ r.set_cookie(COOKIE, {value: data.to_json, path: "/", expires: 1.month.from_now})
40
+
41
+ uri = URI.parse(callback_path)
42
+ uri.query = URI.encode_www_form(data)
43
+ r.redirect(uri)
44
+
45
+ return r.finish
46
+ end
47
+
48
+ build_form.to_response
49
+ end
50
+
51
+ def build_form
52
+ token = begin
53
+ verifier = CSRFTokenVerifier.new
54
+ verifier.call(env)
55
+ verifier.form_authenticity_token
56
+ end
57
+
58
+ request = Rack::Request.new(env)
59
+ raw_defaults = request.cookies[COOKIE] || "{}"
60
+ defaults = JSON.parse(raw_defaults) rescue {}
61
+ defaults["uid"] = SecureRandom.hex(8) unless defaults["uid"].present?
62
+ defaults["email_verified"] = "true" unless defaults["email_verified"].present?
63
+
64
+ OmniAuth::Form.build(:title => "Fake Authentication Provider") do
65
+ html "\n<input type='hidden' name='authenticity_token' value='#{token}'/>"
66
+
67
+ FIELDS.each do |f|
68
+ label_field(f, f)
69
+ if f == "email_verified"
70
+ html "<input type='checkbox' id='#{f}' name='#{f}' value='true' #{"checked" if defaults[f] == "true"}/>"
71
+ else
72
+ html "<input type='text' id='#{f}' name='#{f}' value='#{defaults[f]}'/>"
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def callback_phase
79
+ return unless is_allowed?
80
+ super
81
+ end
82
+
83
+ def auth_hash
84
+ info = request.params.slice(*FIELDS)
85
+ uid = info.delete("uid")
86
+ email_verified = (info.delete("email_verified") == "true")
87
+ OmniAuth::Utils.deep_merge(super, {
88
+ 'uid' => uid,
89
+ 'info' => info,
90
+ 'extra' => { "raw_info" => { "email_verified" => email_verified } }
91
+ })
92
+ end
93
+
94
+ def is_allowed?
95
+ return true if DiscourseDev.config.allow_anonymous_to_impersonate
96
+ fail!("Enable `allow_anonymous_to_impersonate` setting in `config/dev.yml` file.")
97
+ false
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ class DevelopmentAuthenticator < Auth::ManagedAuthenticator
104
+ def name
105
+ 'developmentauth'
106
+ end
107
+
108
+ def can_revoke?
109
+ true
110
+ end
111
+
112
+ def can_connect_existing_user?
113
+ true
114
+ end
115
+
116
+ def enabled?
117
+ DiscourseDev.auth_plugin_enabled?
118
+ end
119
+
120
+ def register_middleware(omniauth)
121
+ omniauth.provider :development, name: :developmentauth
122
+ end
123
+
124
+ def primary_email_verified?(auth)
125
+ auth['extra']['raw_info']['email_verified']
126
+ end
127
+ end
128
+
129
+ auth_provider authenticator: DevelopmentAuthenticator.new
130
+
131
+
132
+ ### DiscourseConnect
133
+ after_initialize do
134
+ module ::DevelopmentAuth
135
+ class Engine < ::Rails::Engine
136
+ engine_name PLUGIN_NAME
137
+ isolate_namespace ::DevelopmentAuth
138
+ end
139
+ end
140
+
141
+ class ::DevelopmentAuth::FakeDiscourseConnectController < ::ApplicationController
142
+ requires_plugin "discourse-development-auth"
143
+
144
+ skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required, :verify_authenticity_token
145
+
146
+ SIMPLE_FIELDS = %w{
147
+ external_id
148
+ email
149
+ username
150
+ name
151
+ }
152
+ ADVANCED_FIELDS = SingleSignOn::ACCESSORS.map(&:to_s) - SIMPLE_FIELDS
153
+ FIELDS = SIMPLE_FIELDS + ADVANCED_FIELDS
154
+
155
+ BOOLS = SingleSignOn::BOOLS.map(&:to_s)
156
+
157
+ COOKIE = "development-auth-discourseconnect-defaults"
158
+
159
+ def auth
160
+ return unless is_allowed?
161
+
162
+ params.require(:sso)
163
+ @payload = request.query_string
164
+ sso = SingleSignOn.parse(@payload, SiteSetting.discourse_connect_secret)
165
+
166
+ if request.method == "POST" && params[:external_id]
167
+ data = {}
168
+ FIELDS.each do |f|
169
+ sso.send(:"#{f}=", params[f])
170
+ data[f] = params[f]
171
+ cookies[COOKIE] = { value: data.to_json, path: "/", expires: 1.month.from_now }
172
+ end
173
+
174
+ return redirect_to sso.to_url(sso.return_sso_url)
175
+ end
176
+
177
+ raw_defaults = cookies[COOKIE] || "{}"
178
+ @defaults = JSON.parse(raw_defaults) rescue {}
179
+ @defaults["return_sso_url"] = sso.return_sso_url
180
+ @defaults["nonce"] = sso.nonce
181
+ @defaults["external_id"] = SecureRandom.hex(8) unless @defaults["external_id"].present?
182
+ render_form
183
+ end
184
+
185
+ private
186
+
187
+ def render_form
188
+ @simple_fields = SIMPLE_FIELDS
189
+ @advanced_fields = ADVANCED_FIELDS
190
+ @bools = BOOLS
191
+ append_view_path(File.expand_path("../app/views", __FILE__))
192
+ render template: "fake_discourse_connect/form", layout: false
193
+ end
194
+ end
195
+
196
+ DevelopmentAuth::Engine.routes.draw do
197
+ get "/fake-discourse-connect" => "fake_discourse_connect#auth"
198
+ post "/fake-discourse-connect" => "fake_discourse_connect#auth"
199
+ end
200
+
201
+ Discourse::Application.routes.append do
202
+ mount ::DevelopmentAuth::Engine, at: "/development-auth"
203
+ end
204
+
205
+ DiscourseSingleSignOn.singleton_class.prepend(Module.new do
206
+ def sso_url
207
+ if DiscourseDev.auth_plugin_enabled?
208
+ return "#{Discourse.base_path}/development-auth/fake-discourse-connect"
209
+ end
210
+ super
211
+ end
212
+ end)
213
+
214
+ EnableSsoValidator.prepend(Module.new do
215
+ def valid_value?(val)
216
+ return true if DiscourseDev.auth_plugin_enabled?
217
+ super
218
+ end
219
+ end)
220
+ end
data/config/dev.yml ADDED
@@ -0,0 +1,11 @@
1
+ site_settings:
2
+ tagging_enabled: true
3
+ verbose_discourse_connect_logging: true
4
+ seed: 1
5
+ start_date: "01/01/2020"
6
+ max_likes_count: 10
7
+ auth_plugin_enabled: true
8
+ allow_anonymous_to_impersonate: false
9
+ new_user:
10
+ username: new_user
11
+ email: new_user@example.com
@@ -0,0 +1,6 @@
1
+ en:
2
+ js:
3
+ login:
4
+ developmentauth:
5
+ title: DevelopmentAuth
6
+ name: DevelopmentAuth
@@ -93,7 +93,7 @@ en:
93
93
  - Totally amped about the 80s
94
94
  - Do microwave ovens kill bacteria?
95
95
  - Most inspirational movie you have ever seen?
96
- - Catching all 151 in 2 hours
96
+ - Catching all 151 in 2 hours 😀
97
97
  - Charlie The Unicorn 4
98
98
  - Video Games for Pre-Teens?
99
99
  - Online learning
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ DiscourseDev::Engine.routes.draw do
4
+ get ':username_or_email/become' => 'admin/impersonate#create', constraints: AdminConstraint.new
5
+ end
data/lib/discourse_dev.rb CHANGED
@@ -4,9 +4,52 @@ require 'i18n'
4
4
 
5
5
  Dir[File.dirname(__FILE__) + '/**/*.rb'].each {|file| require file }
6
6
 
7
- I18n.load_path += Dir[File.join(__dir__, 'faker', 'locales', '**/*.yml')]
8
- I18n.reload! if I18n.backend.initialized?
9
-
10
7
  module DiscourseDev
11
- require 'discourse_dev/railtie' if defined?(Rails)
8
+ require 'discourse_dev/railtie'
9
+ require 'discourse_dev/engine'
10
+
11
+ def self.auth_plugin_enabled?
12
+ config.auth_plugin_enabled
13
+ end
14
+
15
+ def self.config
16
+ @config ||= Config.new
17
+ end
18
+
19
+ def self.auth_plugin
20
+ return unless auth_plugin_enabled?
21
+
22
+ @auth_plugin ||= begin
23
+ path = File.join(root, 'auth', 'plugin.rb')
24
+ source = File.read(path)
25
+ metadata = Plugin::Metadata.parse(source)
26
+ Plugin::Instance.new(metadata, path)
27
+ end
28
+ end
29
+
30
+ def self.settings_file
31
+ File.join(root, "config", "settings.yml")
32
+ end
33
+
34
+ def self.client_locale_files(locale_str)
35
+ Dir[File.join(root, "config", "locales", "client*.#{locale_str}.yml")]
36
+ end
37
+
38
+ def self.root
39
+ File.expand_path("..", __dir__)
40
+ end
41
+ end
42
+
43
+ require "active_record/database_configurations"
44
+
45
+ ActiveRecord::Tasks::DatabaseTasks.module_eval do
46
+ alias_method :rails_each_current_configuration, :each_current_configuration
47
+
48
+ private
49
+ def each_current_configuration(environment, name = nil)
50
+ rails_each_current_configuration(environment, name) { |db_config|
51
+ next if environment == "development" && ENV["SKIP_TEST_DATABASE"] == "1" && db_config["database"] != "discourse_development"
52
+ yield db_config
53
+ }
54
+ end
12
55
  end
@@ -1,18 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rails'
4
+ require 'highline/import'
4
5
 
5
6
  module DiscourseDev
6
7
  class Config
7
- attr_reader :config, :default_config
8
+ attr_reader :config, :default_config, :file_path
8
9
 
9
10
  def initialize
10
- @default_config = YAML.load_file(File.join(File.expand_path(__dir__), "config.yml"))
11
- file_path = File.join(Rails.root, "config", "dev.yml")
11
+ default_file_path = File.join(DiscourseDev.root, "config", "dev.yml")
12
+ @file_path = File.join(Rails.root, "config", "dev.yml")
13
+ @default_config = YAML.load_file(default_file_path)
12
14
 
13
15
  if File.exists?(file_path)
14
16
  @config = YAML.load_file(file_path)
15
17
  else
18
+ puts "I did no detect a custom `config/dev.yml` file, creating one for you where you can amend defaults."
19
+ FileUtils.cp(default_file_path, file_path)
16
20
  @config = {}
17
21
  end
18
22
  end
@@ -20,6 +24,7 @@ module DiscourseDev
20
24
  def update!
21
25
  update_site_settings
22
26
  create_admin_user
27
+ create_new_user
23
28
  set_seed
24
29
  end
25
30
 
@@ -51,27 +56,101 @@ module DiscourseDev
51
56
  settings = config["admin"]
52
57
 
53
58
  if settings.present?
54
- email = settings["email"]
59
+ create_admin_user_from_settings(settings)
60
+ else
61
+ create_admin_user_from_input
62
+ end
63
+ end
64
+
65
+ def create_new_user
66
+ settings = config["new_user"]
55
67
 
56
- admin = ::User.create!(
68
+ if settings.present?
69
+ email = settings["email"] || "new_user@example.com"
70
+
71
+ new_user = ::User.create!(
57
72
  email: email,
58
- username: settings["username"] || UserNameSuggester.suggest(email),
59
- password: settings["password"]
73
+ username: settings["username"] || UserNameSuggester.suggest(email)
60
74
  )
61
- admin.grant_admin!
62
- if admin.trust_level < 1
63
- admin.change_trust_level!(1)
64
- end
65
- admin.email_tokens.update_all confirmed: true
66
- admin.activate
67
- else
68
- Rake::Task['admin:create'].invoke
75
+ new_user.email_tokens.update_all confirmed: true
76
+ new_user.activate
69
77
  end
70
78
  end
71
79
 
72
80
  def set_seed
73
- seed = config["seed"] || default_config["seed"] || 1
81
+ seed = self.seed || 1
74
82
  Faker::Config.random = Random.new(seed)
75
83
  end
84
+
85
+ def start_date
86
+ DateTime.parse(config["start_date"] || default_config["start_date"])
87
+ end
88
+
89
+ def method_missing(name)
90
+ name = name.to_s
91
+ return config[name] if config.keys.include?(name)
92
+ default_config[name]
93
+ end
94
+
95
+ def create_admin_user_from_settings(settings)
96
+ email = settings["email"]
97
+
98
+ admin = ::User.create!(
99
+ email: email,
100
+ username: settings["username"] || UserNameSuggester.suggest(email),
101
+ password: settings["password"]
102
+ )
103
+ admin.grant_admin!
104
+ if admin.trust_level < 1
105
+ admin.change_trust_level!(1)
106
+ end
107
+ admin.email_tokens.update_all confirmed: true
108
+ admin.activate
109
+ end
110
+
111
+ def create_admin_user_from_input
112
+ begin
113
+ email = ask("Email: ")
114
+ password = ask("Password (optional, press ENTER to skip): ")
115
+ username = UserNameSuggester.suggest(email)
116
+
117
+ admin = ::User.new(
118
+ email: email,
119
+ username: username
120
+ )
121
+
122
+ if password.present?
123
+ admin.password = password
124
+ else
125
+ puts "Once site is running use https://localhost:9292/user/#{username}/become to access the account in development"
126
+ end
127
+
128
+ admin.name = ask("Full name: ") if SiteSetting.full_name_required
129
+ saved = admin.save
130
+
131
+ if saved
132
+ File.open(file_path, 'a') do | file|
133
+ file.puts("admin:")
134
+ file.puts(" username: #{admin.username}")
135
+ file.puts(" email: #{admin.email}")
136
+ file.puts(" password: #{password}") if password.present?
137
+ end
138
+ else
139
+ say(admin.errors.full_messages.join("\n"))
140
+ end
141
+ end while !saved
142
+
143
+ admin.active = true
144
+ admin.save
145
+
146
+ admin.grant_admin!
147
+ if admin.trust_level < 1
148
+ admin.change_trust_level!(1)
149
+ end
150
+ admin.email_tokens.update_all confirmed: true
151
+ admin.activate
152
+
153
+ say("\nAdmin account created successfully with username #{admin.username}")
154
+ end
76
155
  end
77
156
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DiscourseDev
4
+ class Engine < Rails::Engine; end
5
+ end
@@ -18,7 +18,8 @@ module DiscourseDev
18
18
  name: Faker::Discourse.unique.group,
19
19
  public_exit: Faker::Boolean.boolean,
20
20
  public_admission: Faker::Boolean.boolean,
21
- primary_group: Faker::Boolean.boolean
21
+ primary_group: Faker::Boolean.boolean,
22
+ created_at: Faker::Time.between(from: DiscourseDev.config.start_date, to: DateTime.now),
22
23
  }
23
24
  end
24
25
 
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'discourse_dev/record'
4
+ require 'faker'
5
+
6
+ module DiscourseDev
7
+ class Post < Record
8
+
9
+ attr_reader :topic
10
+
11
+ def initialize(topic, count = DEFAULT_COUNT)
12
+ super(::Post, count)
13
+ @topic = topic
14
+
15
+ category = topic.category
16
+ @max_likes_count = DiscourseDev.config.max_likes_count
17
+ unless category.groups.blank?
18
+ group_ids = category.groups.pluck(:id)
19
+ @user_ids = ::GroupUser.where(group_id: group_ids).pluck(:user_id)
20
+ @user_count = @user_ids.count
21
+ @max_likes_count = @user_count - 1
22
+ end
23
+ end
24
+
25
+ def data
26
+ {
27
+ topic_id: topic.id,
28
+ raw: Faker::Markdown.sandwich(sentences: 5),
29
+ created_at: Faker::Time.between(from: topic.last_posted_at, to: DateTime.now),
30
+ skip_validations: true,
31
+ skip_guardian: true
32
+ }
33
+ end
34
+
35
+ def create!
36
+ post = PostCreator.new(user, data).create!
37
+ topic.reload
38
+ generate_likes(post)
39
+ end
40
+
41
+ def generate_likes(post)
42
+ user_ids = [post.user_id]
43
+
44
+ Faker::Number.between(from: 0, to: @max_likes_count).times do
45
+ user = self.user
46
+ next if user_ids.include?(user.id)
47
+
48
+ PostActionCreator.new(user, post, PostActionType.types[:like], created_at: Faker::Time.between(from: post.created_at, to: DateTime.now)).perform
49
+ user_ids << user.id
50
+ end
51
+ end
52
+
53
+ def user
54
+ return User.random if topic.category.groups.blank?
55
+ return Discourse.system_user if @user_ids.blank?
56
+
57
+ position = Faker::Number.between(from: 0, to: @user_count - 1)
58
+ ::User.find(@user_ids[position])
59
+ end
60
+
61
+ def populate!
62
+ generate_likes(topic.first_post)
63
+
64
+ @count.times do |i|
65
+ @index = i
66
+ create!
67
+ end
68
+ end
69
+
70
+ def self.add_replies!(args)
71
+ if !args[:topic_id]
72
+ puts "Topic ID is required. Aborting."
73
+ return
74
+ end
75
+
76
+ if !::Topic.find_by_id(args[:topic_id])
77
+ puts "Topic ID does not match topic in DB, aborting."
78
+ return
79
+ end
80
+
81
+ topic = ::Topic.find_by_id(args[:topic_id])
82
+ count = args[:count] ? args[:count].to_i : 50
83
+
84
+ puts "Creating #{count} replies in '#{topic.title}'"
85
+
86
+ count.times do |i|
87
+ @index = i
88
+ begin
89
+ reply = {
90
+ topic_id: topic.id,
91
+ raw: Faker::Markdown.sandwich(sentences: 5),
92
+ skip_validations: true
93
+ }
94
+ PostCreator.new(User.random, reply).create!
95
+ rescue ActiveRecord::RecordNotSaved => e
96
+ puts e
97
+ end
98
+ end
99
+
100
+ puts "Done!"
101
+ end
102
+
103
+ end
104
+ end
@@ -12,6 +12,8 @@ module DiscourseDev
12
12
 
13
13
  def initialize(model, count = DEFAULT_COUNT)
14
14
  Faker::Discourse.unique.clear
15
+ RateLimiter.disable
16
+
15
17
  @model = model
16
18
  @type = model.to_s
17
19
  @count = count
@@ -24,6 +26,17 @@ module DiscourseDev
24
26
  end
25
27
 
26
28
  def populate!
29
+ if current_count >= @count
30
+ puts "Already have #{@count}+ #{type.downcase} records."
31
+
32
+ Rake.application.top_level_tasks.each do |task_name|
33
+ Rake::Task[task_name].reenable
34
+ end
35
+
36
+ Rake::Task['dev:repopulate'].invoke
37
+ return
38
+ end
39
+
27
40
  puts "Creating #{@count} sample #{type.downcase} records"
28
41
 
29
42
  @count.times do |i|
@@ -39,6 +52,10 @@ module DiscourseDev
39
52
  @index
40
53
  end
41
54
 
55
+ def current_count
56
+ model.count
57
+ end
58
+
42
59
  def self.populate!
43
60
  self.new.populate!
44
61
  end
@@ -4,6 +4,9 @@ def check_environment!
4
4
  if !Rails.env.development?
5
5
  raise "Database commands are only supported in development environment"
6
6
  end
7
+
8
+ ENV['SKIP_TEST_DATABASE'] = "1"
9
+ ENV['SKIP_MULTISITE'] = "1"
7
10
  end
8
11
 
9
12
  desc 'Run db:migrate:reset task and populate sample content for development environment'
@@ -17,7 +20,7 @@ end
17
20
 
18
21
  desc 'Initialize development environment'
19
22
  task 'dev:config' => ['db:load_config'] do |_, args|
20
- DiscourseDev::Config.new.update!
23
+ DiscourseDev.config.update!
21
24
  end
22
25
 
23
26
  desc 'Populate sample content for development environment'
@@ -27,4 +30,18 @@ task 'dev:populate' => ['db:load_config'] do |_, args|
27
30
  Rake::Task['categories:populate'].invoke
28
31
  Rake::Task['tags:populate'].invoke
29
32
  Rake::Task['topics:populate'].invoke
33
+ system("redis-cli flushall")
34
+ end
35
+
36
+ desc 'Repopulate sample datas in development environment'
37
+ task 'dev:repopulate' => ['db:load_config'] do |_, args|
38
+ require 'highline/import'
39
+
40
+ answer = ask("Do you want to repopulate the database with fresh data? It will recreate DBs and run migration from scratch before generating all the samples. (Y/n) ")
41
+
42
+ if (answer == "" || answer.downcase == 'y')
43
+ Rake::Task['dev:reset'].invoke
44
+ else
45
+ puts "You can run `dev:reset` rake task to do this repopulate action anytime."
46
+ end
30
47
  end
@@ -24,3 +24,8 @@ desc 'Creates sample topics'
24
24
  task 'topics:populate' => ['db:load_config'] do |_, args|
25
25
  DiscourseDev::Topic.populate!
26
26
  end
27
+
28
+ desc 'Add replies to a topic'
29
+ task 'replies:populate', [:topic_id, :count] => ['db:load_config'] do |_, args|
30
+ DiscourseDev::Post.add_replies!(args)
31
+ end
@@ -11,12 +11,34 @@ module DiscourseDev
11
11
  end
12
12
 
13
13
  def data
14
+ max_views = 0
15
+
16
+ case Faker::Number.between(from: 0, to: 5)
17
+ when 0
18
+ max_views = 10
19
+ when 1
20
+ max_views = 100
21
+ when 2
22
+ max_views = SiteSetting.topic_views_heat_low
23
+ when 3
24
+ max_views = SiteSetting.topic_views_heat_medium
25
+ when 4
26
+ max_views = SiteSetting.topic_views_heat_high
27
+ when 5
28
+ max_views = SiteSetting.topic_views_heat_high + SiteSetting.topic_views_heat_medium
29
+ end
30
+
14
31
  {
15
32
  title: title[0, SiteSetting.max_topic_title_length],
16
33
  raw: Faker::Markdown.sandwich(sentences: 5),
17
34
  category: @category.id,
35
+ created_at: Faker::Time.between(from: DiscourseDev.config.start_date, to: DateTime.now),
18
36
  tags: tags,
19
- topic_opts: { custom_fields: { dev_sample: true } },
37
+ topic_opts: {
38
+ import_mode: true,
39
+ views: Faker::Number.between(from: 1, to: max_views),
40
+ custom_fields: { dev_sample: true }
41
+ },
20
42
  skip_validations: true
21
43
  }
22
44
  end
@@ -41,17 +63,31 @@ module DiscourseDev
41
63
 
42
64
  def create!
43
65
  @category = Category.random
44
- PostCreator.new(user, data).create!
66
+ topic = data
67
+ post = PostCreator.new(user, topic).create!
68
+
69
+ if topic[:title] == "Coolest thing you have seen today"
70
+ reply_count = 99
71
+ else
72
+ reply_count = Faker::Number.between(from: 0, to: 12)
73
+ end
74
+
75
+ Post.new(post.topic, reply_count).populate!
45
76
  end
46
77
 
47
78
  def user
48
79
  return User.random if @category.groups.blank?
49
-
80
+
50
81
  group_ids = @category.groups.pluck(:id)
51
82
  user_ids = ::GroupUser.where(group_id: group_ids).pluck(:user_id)
52
83
  user_count = user_ids.count
53
84
  position = Faker::Number.between(from: 0, to: user_count - 1)
54
85
  ::User.find(user_ids[position] || Discourse::SYSTEM_USER_ID)
55
86
  end
87
+
88
+ def current_count
89
+ category_definition_topic_ids = ::Category.pluck(:topic_id)
90
+ ::Topic.where(archetype: :regular).where.not(id: category_definition_topic_ids).count
91
+ end
56
92
  end
57
93
  end
@@ -6,9 +6,14 @@ require 'faker'
6
6
 
7
7
  module DiscourseDev
8
8
  class User < Record
9
+ attr_reader :images
9
10
 
10
11
  def initialize(count = DEFAULT_COUNT)
11
12
  super(::User, count)
13
+
14
+ # Using the stock avatar images from https://tinyfac.es
15
+ # Tiny Faces is a free crowd-sourced avatar gallery
16
+ @images = Dir[File.join(__dir__, '..', '..', 'avatars', '*.*')]
12
17
  end
13
18
 
14
19
  def data
@@ -23,13 +28,15 @@ module DiscourseDev
23
28
  username: username,
24
29
  username_lower: username_lower,
25
30
  moderator: Faker::Boolean.boolean(true_ratio: 0.1),
26
- trust_level: Faker::Number.between(from: 1, to: 4)
31
+ trust_level: Faker::Number.between(from: 1, to: 4),
32
+ created_at: Faker::Time.between(from: DiscourseDev.config.start_date, to: DateTime.now),
27
33
  }
28
34
  end
29
35
 
30
36
  def create!
31
37
  super do |user|
32
38
  user.activate
39
+ set_random_avatar(user)
33
40
  Faker::Number.between(from: 0, to: 2).times do
34
41
  group = Group.random
35
42
 
@@ -41,5 +48,48 @@ module DiscourseDev
41
48
  def self.random
42
49
  super(::User)
43
50
  end
51
+
52
+ def set_random_avatar(user)
53
+ return if images.blank?
54
+ return unless Faker::Boolean.boolean
55
+
56
+ avatar_index = Faker::Number.between(from: 0, to: images.count - 1)
57
+ avatar_path = images[avatar_index]
58
+ create_avatar(user, avatar_path)
59
+ @images.delete_at(avatar_index)
60
+ end
61
+
62
+ def create_avatar(user, avatar_path)
63
+ tempfile = copy_to_tempfile(avatar_path)
64
+ filename = "avatar#{File.extname(avatar_path)}"
65
+ upload = UploadCreator.new(tempfile, filename, type: "avatar").create_for(user.id)
66
+
67
+ if upload.present? && upload.persisted?
68
+ user.create_user_avatar
69
+ user.user_avatar.update(custom_upload_id: upload.id)
70
+ user.update(uploaded_avatar_id: upload.id)
71
+ else
72
+ STDERR.puts "Failed to upload avatar for user #{user.username}: #{avatar_path}"
73
+ STDERR.puts upload.errors.inspect if upload
74
+ end
75
+ rescue
76
+ STDERR.puts "Failed to create avatar for user #{user.username}: #{avatar_path}"
77
+ ensure
78
+ tempfile.close! if tempfile
79
+ end
80
+
81
+ private
82
+
83
+ def copy_to_tempfile(source_path)
84
+ extension = File.extname(source_path)
85
+ tmp = Tempfile.new(['discourse-upload', extension])
86
+
87
+ File.open(source_path) do |source_stream|
88
+ IO.copy_stream(source_stream, tmp)
89
+ end
90
+
91
+ tmp.rewind
92
+ tmp
93
+ end
44
94
  end
45
95
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DiscourseDev
4
- VERSION = "0.0.4"
4
+ VERSION = "0.1.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: discourse_dev
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vinoth Kannan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-11 00:00:00.000000000 Z
11
+ date: 2021-04-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faker
@@ -65,12 +65,32 @@ files:
65
65
  - LICENSE.txt
66
66
  - README.md
67
67
  - Rakefile
68
+ - auth/app/views/fake_discourse_connect/form.html.erb
69
+ - auth/plugin.rb
70
+ - avatars/03F55412-DE8A-4F83-AAA6-D67EE5CE48DA-200w.jpeg
71
+ - avatars/1C4EEDC2-FE9C-40B3-A2C9-A038873EE692-200w.jpeg
72
+ - avatars/26CFEFB3-21C8-49FC-8C19-8E6A62B6D2E0-200w.jpeg
73
+ - avatars/282A12CA-E0D7-4011-8BDD-1FAFAAB035F7-200w.jpeg
74
+ - avatars/2DDDE973-40EC-4004-ABC0-73FD4CD6D042-200w.jpeg
75
+ - avatars/344CFC24-61FB-426C-B3D1-CAD5BCBD3209-200w.jpeg
76
+ - avatars/852EC6E1-347C-4187-9D42-DF264CCF17BF-200w.jpeg
77
+ - avatars/A7299C8E-CEFC-47D9-939A-3C8CA0EA4D13-200w.jpeg
78
+ - avatars/AEF44435-B547-4B84-A2AE-887DFAEE6DDF-200w.jpeg
79
+ - avatars/B3CF5288-34B0-4A5E-9877-5965522529D6-200w.jpeg
80
+ - avatars/BA0CB1F2-8C79-4376-B13B-DD5FB8772537-200w.jpeg
81
+ - avatars/E0B4CAB3-F491-4322-BEF2-208B46748D4A-200w.jpeg
82
+ - avatars/FBEBF655-4886-455A-A4A4-D62B77DD419B-200w.jpeg
83
+ - config/dev.yml
84
+ - config/locales/client.en.yml
85
+ - config/locales/faker.en.yml
86
+ - config/routes.rb
68
87
  - discourse_dev.gemspec
69
88
  - lib/discourse_dev.rb
70
89
  - lib/discourse_dev/category.rb
71
90
  - lib/discourse_dev/config.rb
72
- - lib/discourse_dev/config.yml
91
+ - lib/discourse_dev/engine.rb
73
92
  - lib/discourse_dev/group.rb
93
+ - lib/discourse_dev/post.rb
74
94
  - lib/discourse_dev/railtie.rb
75
95
  - lib/discourse_dev/record.rb
76
96
  - lib/discourse_dev/tag.rb
@@ -80,7 +100,6 @@ files:
80
100
  - lib/discourse_dev/user.rb
81
101
  - lib/discourse_dev/version.rb
82
102
  - lib/faker/discourse.rb
83
- - lib/faker/locales/en.yml
84
103
  homepage: https://github.com/discourse/discourse_dev
85
104
  licenses:
86
105
  - MIT
@@ -1,4 +0,0 @@
1
- site_settings:
2
- tagging_enabled: true
3
- verbose_discourse_connect_logging: true
4
- seed: 1