discourse_dev 0.0.4 → 0.1.0

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