bible270 0.17.1 → 0.18.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +70 -29
- data/README.md +28 -2
- data/Rakefile +3 -0
- data/app/controllers/bible270/admin_controller.rb +80 -0
- data/app/controllers/bible270/profiles_controller.rb +2 -0
- data/app/controllers/bible270/sessions_controller.rb +3 -1
- data/app/helpers/bible270/plan_helper.rb +15 -4
- data/app/mailers/bible270/broadcast.html.erb +17 -0
- data/app/mailers/bible270/broadcast.text.erb +7 -0
- data/app/mailers/bible270/notice_mailer.rb +20 -0
- data/app/models/bible270/reader.rb +41 -0
- data/app/views/bible270/admin/index.html.erb +27 -0
- data/app/views/bible270/admin/show.html.erb +23 -0
- data/app/views/bible270/days/_reading.html.erb +2 -2
- data/app/views/bible270/notice_mailer/broadcast.html.erb +17 -0
- data/app/views/bible270/notice_mailer/broadcast.text.erb +7 -0
- data/app/views/bible270/profiles/edit.html.erb +1 -0
- data/app/views/bible270/shared/_passage_source.html.erb +13 -0
- data/app/views/bible270/shared/_styles.html.erb +10 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20260101000014_add_blue_letter_bible_to_bible270_readers.rb +7 -0
- data/db/migrate/20260101000015_add_passage_source_to_bible270_readers.rb +26 -0
- data/lib/bible270/configuration.rb +1 -6
- data/lib/bible270/migration_reconciler.rb +252 -0
- data/lib/bible270/translations.rb +61 -5
- data/lib/bible270/version.rb +1 -1
- data/lib/tasks/bible270_migrations.rake +57 -0
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7ba4b388cfe909e84f06899d72159471aab1a506154fc72854659d64320a3390
|
|
4
|
+
data.tar.gz: ebcf629ca6a12f57bf87d72073115c9a4655cec15e018e9349813aaab11a2e41
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b202223f06388e4b7f95d53b52f84cc84018ed7ac7ad8e49c48467525e7d76a0bd60185e46bd9b6223a83d7939666b6d7e7d6f1360ca625967452691d1fc528
|
|
7
|
+
data.tar.gz: 8c0b363492837cf5fb715ad13effdee46829cb34dad426e2bbfa8b24c4834f61ea16b2411b5c2e0fc0098d6b0fa397e8fe0cee487a763670f75f8f9fbb2bbb6c
|
data/CHANGELOG.md
CHANGED
|
@@ -6,7 +6,51 @@ All notable changes to bible270. Format follows [Keep a Changelog](https://keepa
|
|
|
6
6
|
|
|
7
7
|
See the changes since the last release:
|
|
8
8
|
|
|
9
|
-
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.
|
|
9
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.17.2...main)**
|
|
10
|
+
|
|
11
|
+
## [0.17.2] - 2026-08-08
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- reader Bible translation preferences editable by admin users
|
|
16
|
+
- admin broadcast email tools
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- extract first and last names from OmniAuth display names
|
|
21
|
+
- expand CI coverage reporting and add Ruby 4.0 coverage
|
|
22
|
+
|
|
23
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.17.1...v0.17.2)**
|
|
24
|
+
|
|
25
|
+
## [0.17.1] - 2026-08-06
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- likes for reflections
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- display timestamps using the local time zone
|
|
34
|
+
- rename reflections to 'posts' in UI headers, because we like alliteration
|
|
35
|
+
|
|
36
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.17.0...v0.17.1)**
|
|
37
|
+
|
|
38
|
+
## [0.17.0] - 2026-08-05
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- a reflections page for browsing recent comments
|
|
43
|
+
- the ability for readers to edit their reflections
|
|
44
|
+
|
|
45
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.16.1...v0.17.0)**
|
|
46
|
+
|
|
47
|
+
## [0.16.1] - 2026-08-05
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- refactor mailer URL helpers to use the engine's routes
|
|
52
|
+
|
|
53
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.16.0...v0.16.1)**
|
|
10
54
|
|
|
11
55
|
## [0.16.0] - 2026-08-05
|
|
12
56
|
|
|
@@ -19,8 +63,6 @@ See the changes since the last release:
|
|
|
19
63
|
|
|
20
64
|
## [0.15.3] - 2026-08-04
|
|
21
65
|
|
|
22
|
-
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.15.2...v0.15.3)**
|
|
23
|
-
|
|
24
66
|
### Added
|
|
25
67
|
|
|
26
68
|
- `Bible270::Checkoff.reset_column_information` to migrations to prevent `UnknownAttributeError` during backfills.
|
|
@@ -30,9 +72,9 @@ See the changes since the last release:
|
|
|
30
72
|
|
|
31
73
|
- 'Today' now derived from system time, not UTC
|
|
32
74
|
|
|
33
|
-
|
|
75
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.15.2...v0.15.3)**
|
|
34
76
|
|
|
35
|
-
|
|
77
|
+
## [0.15.2] - 2026-08-04
|
|
36
78
|
|
|
37
79
|
### Added
|
|
38
80
|
|
|
@@ -42,9 +84,9 @@ See the changes since the last release:
|
|
|
42
84
|
|
|
43
85
|
- consistent reader sort order by first name in 'People' and 'Admin'
|
|
44
86
|
|
|
45
|
-
|
|
87
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.15.1...v0.15.2)**
|
|
46
88
|
|
|
47
|
-
|
|
89
|
+
## [0.15.1] - 2026-08-03
|
|
48
90
|
|
|
49
91
|
### Added
|
|
50
92
|
|
|
@@ -59,9 +101,9 @@ See the changes since the last release:
|
|
|
59
101
|
|
|
60
102
|
- old zip file of tests
|
|
61
103
|
|
|
62
|
-
|
|
104
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.15.0...v0.15.1)**
|
|
63
105
|
|
|
64
|
-
|
|
106
|
+
## [0.15.0] - 2026-08-02
|
|
65
107
|
|
|
66
108
|
### Added
|
|
67
109
|
|
|
@@ -73,9 +115,9 @@ See the changes since the last release:
|
|
|
73
115
|
|
|
74
116
|
- mobile session requiring sign-in every time: use persistent sessions using 'remember-me' cookie
|
|
75
117
|
|
|
76
|
-
|
|
118
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.14.1...v0.15.0)**
|
|
77
119
|
|
|
78
|
-
|
|
120
|
+
## [0.14.1] - 2026-07-30
|
|
79
121
|
|
|
80
122
|
### Changed
|
|
81
123
|
|
|
@@ -87,9 +129,9 @@ See the changes since the last release:
|
|
|
87
129
|
- display 'Start' not 'Continue' if reader is on day 1
|
|
88
130
|
- adjust badge vertical alignment in styles
|
|
89
131
|
|
|
90
|
-
|
|
132
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.14.0...v0.14.1)**
|
|
91
133
|
|
|
92
|
-
|
|
134
|
+
## [0.14.0] - 2026-07-30
|
|
93
135
|
|
|
94
136
|
### Added
|
|
95
137
|
|
|
@@ -107,16 +149,10 @@ See the changes since the last release:
|
|
|
107
149
|
wrote an initializer that crashed the host app on boot. A test now renders every generator template
|
|
108
150
|
and compiles the result, across three mount points.
|
|
109
151
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.13.0...v0.13.1)**
|
|
113
|
-
|
|
114
|
-
Nothing consequential.
|
|
152
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.13.0...v0.14.0)**
|
|
115
153
|
|
|
116
154
|
## [0.13.0] — 2026-07-29
|
|
117
155
|
|
|
118
|
-
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.12.0...v0.13.0)**
|
|
119
|
-
|
|
120
156
|
### Added
|
|
121
157
|
|
|
122
158
|
- favicon support
|
|
@@ -127,9 +163,9 @@ Nothing consequential.
|
|
|
127
163
|
|
|
128
164
|
- Correct verb for reading prompt: 'Start' and 'Continue
|
|
129
165
|
|
|
130
|
-
|
|
166
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.12.0...v0.13.0)**
|
|
131
167
|
|
|
132
|
-
|
|
168
|
+
## [0.12.0] — 2026-07-29
|
|
133
169
|
|
|
134
170
|
### Added
|
|
135
171
|
|
|
@@ -141,9 +177,9 @@ Nothing consequential.
|
|
|
141
177
|
- Top menu shows 'Progress' in place of user display name
|
|
142
178
|
- Dependabot gem updates to minitest and rubocop
|
|
143
179
|
|
|
144
|
-
|
|
180
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.11.0...v0.12.0)**
|
|
145
181
|
|
|
146
|
-
|
|
182
|
+
## [0.11.0] — 2026-07-28
|
|
147
183
|
|
|
148
184
|
### Added
|
|
149
185
|
|
|
@@ -195,9 +231,9 @@ Nothing consequential.
|
|
|
195
231
|
- Migrations: `20260101000007` (settings). Active Storage tables are the host app's
|
|
196
232
|
(`bin/rails active_storage:install`).
|
|
197
233
|
|
|
198
|
-
|
|
234
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.10.0...v0.11.0)**
|
|
199
235
|
|
|
200
|
-
|
|
236
|
+
## [0.10.0] — 2026-07-26
|
|
201
237
|
|
|
202
238
|
### Reading plan
|
|
203
239
|
|
|
@@ -270,9 +306,9 @@ Nothing consequential.
|
|
|
270
306
|
|
|
271
307
|
- Table names changed from `bible_reading_plan_*` in the 0.9.0 line; this is a fresh install.
|
|
272
308
|
|
|
273
|
-
|
|
309
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.9.0...v0.10.0)**
|
|
274
310
|
|
|
275
|
-
|
|
311
|
+
## [0.9.0] — 2026-07-25
|
|
276
312
|
|
|
277
313
|
### Changed
|
|
278
314
|
|
|
@@ -290,6 +326,8 @@ Nothing consequential.
|
|
|
290
326
|
* `Plan.nt_groups`, `format_pp_segment`, `pp_segment_length` (superseded by the shared `format_segment` and `segment_length`), and `totals[:nt_rest_days]`.
|
|
291
327
|
* `nt_rest_days` remains but now always returns an empty array.
|
|
292
328
|
|
|
329
|
+
**[Full Changelog](https://github.com/avonderluft/bible270/compare/v0.6.2...v0.9.0)**
|
|
330
|
+
|
|
293
331
|
## [0.6.2] — 2026-07-25
|
|
294
332
|
|
|
295
333
|
This is the initial public release.
|
|
@@ -352,6 +390,10 @@ This is the initial public release.
|
|
|
352
390
|
|
|
353
391
|
* Email sign-in requires working Action Mailer delivery in the host application. Set `config.mailer_from`; delivery is inline by default, or set `email_sign_in_deliver_later` when a queue backend is available.
|
|
354
392
|
|
|
393
|
+
[0.17.2]: https://github.com/avonderluft/bible270/releases/tag/v0.17.2
|
|
394
|
+
[0.17.1]: https://github.com/avonderluft/bible270/releases/tag/v0.17.1
|
|
395
|
+
[0.17.0]: https://github.com/avonderluft/bible270/releases/tag/v0.17.0
|
|
396
|
+
[0.16.1]: https://github.com/avonderluft/bible270/releases/tag/v0.16.1
|
|
355
397
|
[0.16.0]: https://github.com/avonderluft/bible270/releases/tag/v0.16.0
|
|
356
398
|
[0.15.3]: https://github.com/avonderluft/bible270/releases/tag/v0.15.3
|
|
357
399
|
[0.15.2]: https://github.com/avonderluft/bible270/releases/tag/v0.15.2
|
|
@@ -359,7 +401,6 @@ This is the initial public release.
|
|
|
359
401
|
[0.15.0]: https://github.com/avonderluft/bible270/releases/tag/v0.15.0
|
|
360
402
|
[0.14.1]: https://github.com/avonderluft/bible270/releases/tag/v0.14.1
|
|
361
403
|
[0.14.0]: https://github.com/avonderluft/bible270/releases/tag/v0.14.0
|
|
362
|
-
[0.13.1]: https://github.com/avonderluft/bible270/releases/tag/v0.13.1
|
|
363
404
|
[0.13.0]: https://github.com/avonderluft/bible270/releases/tag/v0.13.0
|
|
364
405
|
[0.12.0]: https://github.com/avonderluft/bible270/releases/tag/v0.12.0
|
|
365
406
|
[0.11.0]: https://github.com/avonderluft/bible270/releases/tag/v0.11.0
|
data/README.md
CHANGED
|
@@ -117,6 +117,29 @@ bin/rails bible270:install:migrations # skips ones you already have
|
|
|
117
117
|
bin/rails db:migrate
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
+
### Reconciling a copied database
|
|
121
|
+
|
|
122
|
+
A copied database can contain the Bible270 tables while its `schema_migrations` versions do not match
|
|
123
|
+
the timestamped migration copies in the local app. In that case, preview the reconciliation:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
bin/rails bible270:migrations:reconcile
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The task recognizes Bible270 migrations—and the related generated Active Storage migration used for
|
|
130
|
+
reader avatars—by name, then verifies their tables, columns, current indexes, and foreign keys. It
|
|
131
|
+
does not treat an existing table alone as sufficient. After reviewing the list and backing up the
|
|
132
|
+
database, record only the migrations already reflected in the schema:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
APPLY=1 bin/rails bible270:migrations:reconcile
|
|
136
|
+
bin/rails db:migrate
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Preview mode never writes to the database. Apply mode only inserts verified local versions into
|
|
140
|
+
`schema_migrations`; it does not create, remove, or alter application tables. Migrations whose changes
|
|
141
|
+
are missing remain pending for the final `db:migrate` command.
|
|
142
|
+
|
|
120
143
|
## Bible270 Mount point in your Rails app
|
|
121
144
|
|
|
122
145
|
The path lives in exactly **one** place, `config.mount_at`:
|
|
@@ -361,7 +384,10 @@ config.favicon = false # none; the host's favicon applies
|
|
|
361
384
|
It's emitted by the engine's layout, so if you point `config.layout` at your own layout, add
|
|
362
385
|
`<%= b270_favicon_tag %>` to its `<head>`.
|
|
363
386
|
|
|
364
|
-
References link to Bible Gateway by default
|
|
387
|
+
References link to Bible Gateway by default. Readers choose Bible Gateway or Blue Letter Bible from
|
|
388
|
+
their profile, and an admin can set the same source for any reader. The `HEB/GRK` option always uses
|
|
389
|
+
Blue Letter Bible: WLC for Old Testament, Psalms, and Proverbs readings, and mGNT for New Testament
|
|
390
|
+
readings. Set `passage_url_builder` to use another default reader, or a reader you host yourself.
|
|
365
391
|
|
|
366
392
|
## With ComfortableMediaSurfer
|
|
367
393
|
|
|
@@ -371,7 +397,7 @@ The CMS serves your content; this is a separate mounted app. Either **just link
|
|
|
371
397
|
|
|
372
398
|
| Table | Holds |
|
|
373
399
|
|-------|-------|
|
|
374
|
-
| `bible270_readers` | identity
|
|
400
|
+
| `bible270_readers` | identity, avatar, provider/uid or polymorphic `owner`, start date, translation and passage-reader preferences |
|
|
375
401
|
| `bible270_checkoffs` | one row per reader per day per track (`ot`/`nt`/`pp`), unique-indexed |
|
|
376
402
|
| `bible270_comments` | a reflection on a day, optionally scoped to a track, public to all |
|
|
377
403
|
| `bible270_sign_in_tokens` | magic-link tokens: email, **digest only**, expiry, consumed-at |
|
data/Rakefile
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'bundler/gem_tasks' # provides build / install / release
|
|
4
4
|
require 'rake/testtask'
|
|
5
|
+
require 'coveralls/rake/task'
|
|
5
6
|
|
|
6
7
|
Rake::TestTask.new(:test) do |t|
|
|
7
8
|
t.libs << 'test' << 'lib'
|
|
@@ -9,4 +10,6 @@ Rake::TestTask.new(:test) do |t|
|
|
|
9
10
|
t.warning = false
|
|
10
11
|
end
|
|
11
12
|
|
|
13
|
+
Coveralls::RakeTask.new
|
|
14
|
+
|
|
12
15
|
task default: :test
|
|
@@ -8,6 +8,9 @@ module Bible270
|
|
|
8
8
|
# neither set the panel is unreachable, and every action 404s rather than 403s
|
|
9
9
|
# so its existence isn't advertised.
|
|
10
10
|
class AdminController < ApplicationController
|
|
11
|
+
LAST_BROADCAST_AT = 'last_broadcast_at'
|
|
12
|
+
LAST_BROADCAST_SUBJECT = 'last_broadcast_subject'
|
|
13
|
+
|
|
11
14
|
before_action :require_admin!
|
|
12
15
|
before_action :load_reader,
|
|
13
16
|
only: %i[show destroy update_start update_profile remove_avatar complete_through toggle_day]
|
|
@@ -32,6 +35,9 @@ module Bible270
|
|
|
32
35
|
counts = Checkoff.group(:reader_id, :day).count
|
|
33
36
|
@days_completed = Hash.new(0)
|
|
34
37
|
counts.each { |(rid, day), n| @days_completed[rid] += 1 if n >= Plan.total_parts(day) }
|
|
38
|
+
@reachable = Reader.where.not(email: [nil, '']).count
|
|
39
|
+
@last_broadcast_at = parsed_time(Setting.read(LAST_BROADCAST_AT))
|
|
40
|
+
@last_broadcast_subject = Setting.read(LAST_BROADCAST_SUBJECT)
|
|
35
41
|
end
|
|
36
42
|
|
|
37
43
|
def show
|
|
@@ -75,6 +81,53 @@ module Bible270
|
|
|
75
81
|
end
|
|
76
82
|
|
|
77
83
|
# Put the reader on a given day as of today, or set an explicit start date.
|
|
84
|
+
def broadcast
|
|
85
|
+
subject = params[:subject].to_s.strip
|
|
86
|
+
body = params[:body].to_s.strip
|
|
87
|
+
|
|
88
|
+
if subject.empty? || body.empty?
|
|
89
|
+
return redirect_to(admin_path, alert: 'A message needs both a subject and something to say.')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
recipients = Reader.where.not(email: [nil, '']).order(:id)
|
|
93
|
+
return redirect_to(admin_path, alert: 'Nobody has an email address.') if recipients.empty?
|
|
94
|
+
|
|
95
|
+
sent = deliver_broadcast(recipients, subject, body)
|
|
96
|
+
Setting.write(LAST_BROADCAST_AT, Time.current.iso8601)
|
|
97
|
+
Setting.write(LAST_BROADCAST_SUBJECT, subject)
|
|
98
|
+
|
|
99
|
+
redirect_to admin_path,
|
|
100
|
+
notice: "Sent to #{sent} #{'reader'.pluralize(sent)}."
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def update_bible_version
|
|
104
|
+
reader = Reader.find_by(id: params[:id])
|
|
105
|
+
return redirect_to(admin_path, alert: 'No such reader.') if reader.nil?
|
|
106
|
+
|
|
107
|
+
code = params[:bible_version].to_s
|
|
108
|
+
source = params[:passage_source].presence || Reader::DEFAULT_PASSAGE_SOURCE
|
|
109
|
+
unless Reader::PASSAGE_SOURCES.include?(source)
|
|
110
|
+
return redirect_to admin_reader_path(reader), alert: 'That is not a reading-link source on the list.'
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Blank means "no preference": the reader follows the site default, which is
|
|
114
|
+
# not the same as choosing whichever translation the site happens to use now.
|
|
115
|
+
if code.empty?
|
|
116
|
+
reader.update(bible_version: nil, passage_source: source)
|
|
117
|
+
return redirect_to admin_reader_path(reader),
|
|
118
|
+
notice: "#{reader.display_name} now follows the site default " \
|
|
119
|
+
"(#{Bible270.config.bible_version})."
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if reader.update_bible_version(code)
|
|
123
|
+
reader.update_column(:passage_source, source)
|
|
124
|
+
redirect_to admin_reader_path(reader),
|
|
125
|
+
notice: "#{reader.display_name} now reads the #{reader.bible_version_label}."
|
|
126
|
+
else
|
|
127
|
+
redirect_to admin_reader_path(reader), alert: 'That is not a translation on the list.'
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
78
131
|
def update_start
|
|
79
132
|
if params[:start_date].present?
|
|
80
133
|
# set_start_date!, not update_start_date!: an admin is not subject to
|
|
@@ -138,6 +191,33 @@ module Bible270
|
|
|
138
191
|
end
|
|
139
192
|
end
|
|
140
193
|
|
|
194
|
+
# Failures are counted rather than raised: one bad address must not stop the
|
|
195
|
+
# rest of the message going out, and the admin is told how many were sent.
|
|
196
|
+
# A stored timestamp that cannot be read is treated as absent rather than
|
|
197
|
+
# blowing up the admin page.
|
|
198
|
+
def parsed_time(value)
|
|
199
|
+
return nil if value.blank?
|
|
200
|
+
|
|
201
|
+
Time.parse(value.to_s)
|
|
202
|
+
rescue ArgumentError, TypeError
|
|
203
|
+
nil
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def deliver_broadcast(recipients, subject, body)
|
|
207
|
+
later = Bible270.config.registration_notice_deliver_later
|
|
208
|
+
sent = 0
|
|
209
|
+
|
|
210
|
+
recipients.find_each do |reader|
|
|
211
|
+
mail = NoticeMailer.broadcast(reader_id: reader.id, subject: subject, body: body)
|
|
212
|
+
later ? mail.deliver_later : mail.deliver_now
|
|
213
|
+
sent += 1
|
|
214
|
+
rescue StandardError => e
|
|
215
|
+
Rails.logger.error("[bible270] broadcast to reader #{reader.id} failed: #{e.class}: #{e.message}")
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
sent
|
|
219
|
+
end
|
|
220
|
+
|
|
141
221
|
private
|
|
142
222
|
|
|
143
223
|
def require_admin!
|
|
@@ -21,6 +21,8 @@ module Bible270
|
|
|
21
21
|
if params[:bible_version].present? && !current_reader.update_bible_version(params[:bible_version])
|
|
22
22
|
problems << 'a translation from the list'
|
|
23
23
|
end
|
|
24
|
+
source = params[:passage_source].presence || Reader::DEFAULT_PASSAGE_SOURCE
|
|
25
|
+
problems << 'a reading-link source from the list' unless current_reader.update_passage_source(source)
|
|
24
26
|
if params[:avatar].present? && !current_reader.attach_avatar(params[:avatar])
|
|
25
27
|
problems << (current_reader.errors[:avatar].first || 'a valid image')
|
|
26
28
|
end
|
|
@@ -36,7 +36,9 @@ module Bible270
|
|
|
36
36
|
remember_reader!(reader)
|
|
37
37
|
@current_reader = reader
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
# first_name is filled in for every route now, but a bridged host user may
|
|
40
|
+
# still arrive without one.
|
|
41
|
+
redirect_to destination, notice: "Welcome #{reader.first_name.presence || reader.display_name}."
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
def destroy
|
|
@@ -112,13 +112,24 @@ module Bible270
|
|
|
112
112
|
def b270_passage_url(reference, version = nil)
|
|
113
113
|
return '#' if reference.blank?
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
selected_version = version || b270_bible_version
|
|
116
|
+
if selected_version != Translations::ORIGINAL_LANGUAGES && current_reader&.blue_letter_bible?
|
|
117
|
+
return Translations.passage_url(reference, selected_version, blue_letter: true)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
Bible270.config.passage_url_builder.call(reference, selected_version)
|
|
116
121
|
end
|
|
117
122
|
|
|
118
123
|
# Small grey "(NKJV)" after a reading, so it's clear which translation the
|
|
119
|
-
# link opens in.
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
# link opens in. The original-language preference names the actual language
|
|
125
|
+
# used for each track rather than repeating the combined preference code.
|
|
126
|
+
def b270_version_tag(version = nil, track: nil)
|
|
127
|
+
label = version || b270_bible_version
|
|
128
|
+
if label == Translations::ORIGINAL_LANGUAGES && track
|
|
129
|
+
label = track.to_s == 'nt' ? 'Greek' : 'Hebrew'
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
content_tag :span, "(#{label})", class: 'b270-version'
|
|
122
133
|
end
|
|
123
134
|
|
|
124
135
|
def b270_avatar_src(reader)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<body style="margin:0;padding:24px;background:#ece6da;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#231f18">
|
|
4
|
+
<div style="max-width:520px;margin:0 auto;background:#f4efe6;border:1px solid #d7cfbf;border-radius:14px;padding:28px">
|
|
5
|
+
<p style="margin:0 0 16px;font-size:15px">
|
|
6
|
+
<%= @reader.first_name.presence || @reader.display_name %>,
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<div style="font-size:15px;line-height:1.6"><%= simple_format(@body) %></div>
|
|
10
|
+
|
|
11
|
+
<p style="margin:24px 0 0;padding-top:16px;border-top:1px solid #d7cfbf;font-size:12px;color:#9a9082">
|
|
12
|
+
<%= @app_name %><% if @plan_url %> ·
|
|
13
|
+
<a href="<%= @plan_url %>" style="color:#a5812c"><%= @plan_url %></a><% end %>
|
|
14
|
+
</p>
|
|
15
|
+
</div>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -31,6 +31,22 @@ module Bible270
|
|
|
31
31
|
subject: "#{@app_name}: #{@author.display_name} mentioned you on day #{@comment.day}"
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
+
# A message from whoever runs the plan to one reader. Sent individually rather
|
|
35
|
+
# than as one email with everyone in bcc: a bcc list of a hundred addresses is
|
|
36
|
+
# a spam signal, and it means nobody can be greeted by name or unsubscribe
|
|
37
|
+
# meaningfully.
|
|
38
|
+
def broadcast(reader_id:, subject:, body:)
|
|
39
|
+
@reader = Reader.find_by(id: reader_id)
|
|
40
|
+
return message.perform_deliveries = false if @reader.nil? || @reader.email.blank?
|
|
41
|
+
return message.perform_deliveries = false if subject.to_s.strip.empty? || body.to_s.strip.empty?
|
|
42
|
+
|
|
43
|
+
@body = body.to_s
|
|
44
|
+
@app_name = Bible270.config.app_name
|
|
45
|
+
@plan_url = plan_url
|
|
46
|
+
|
|
47
|
+
mail to: @reader.email, subject: subject.to_s.strip
|
|
48
|
+
end
|
|
49
|
+
|
|
34
50
|
private
|
|
35
51
|
|
|
36
52
|
# A mailer has no request, so the host is config.mailer_host or nothing. No
|
|
@@ -41,6 +57,10 @@ module Bible270
|
|
|
41
57
|
# config.mount_at was worse than useless — ignored when blank, and overriding
|
|
42
58
|
# the true prefix when not, so a config that disagreed with routes.rb would
|
|
43
59
|
# have produced links to nowhere.
|
|
60
|
+
def plan_url
|
|
61
|
+
engine_url(:root_url)
|
|
62
|
+
end
|
|
63
|
+
|
|
44
64
|
def day_url_for(day)
|
|
45
65
|
engine_url(:day_url, day)
|
|
46
66
|
end
|
|
@@ -25,8 +25,12 @@ module Bible270
|
|
|
25
25
|
# transaction rolls back.
|
|
26
26
|
after_create_commit :notify_of_registration
|
|
27
27
|
|
|
28
|
+
PASSAGE_SOURCES = %w[bible_gateway blue_letter_bible].freeze
|
|
29
|
+
DEFAULT_PASSAGE_SOURCE = 'bible_gateway'
|
|
30
|
+
|
|
28
31
|
validates :display_name, presence: true
|
|
29
32
|
validates :uid, uniqueness: { scope: :provider }, allow_nil: true
|
|
33
|
+
validates :passage_source, inclusion: { in: PASSAGE_SOURCES }
|
|
30
34
|
|
|
31
35
|
# Build/refresh a reader from an OmniAuth auth hash. Tolerant of the various
|
|
32
36
|
# shapes strategies return (OmniAuth::AuthHash, plain Hash, missing info).
|
|
@@ -45,10 +49,37 @@ module Bible270
|
|
|
45
49
|
image = first_present(dig_auth(info, :image), dig_auth(info, :avatar_url))
|
|
46
50
|
reader.email = email if email.present?
|
|
47
51
|
reader.avatar_url = image if image.present?
|
|
52
|
+
assign_names_from(reader, info)
|
|
48
53
|
reader.save
|
|
49
54
|
reader
|
|
50
55
|
end
|
|
51
56
|
|
|
57
|
+
# Providers hand back one display name far more often than two fields, so the
|
|
58
|
+
# name is split when they do not. Without this an OmniAuth reader had no
|
|
59
|
+
# first_name — which made the greeting read "Welcome ." and, more seriously,
|
|
60
|
+
# made them unmentionable: Reader.mentioned_in only considers readers who have
|
|
61
|
+
# one.
|
|
62
|
+
#
|
|
63
|
+
# Only filled in when empty, so a reader who has since corrected their name on
|
|
64
|
+
# their profile does not have it overwritten at every sign-in.
|
|
65
|
+
def self.assign_names_from(reader, info)
|
|
66
|
+
return if reader.first_name.present?
|
|
67
|
+
|
|
68
|
+
given = dig_auth(info, :first_name)
|
|
69
|
+
family = dig_auth(info, :last_name)
|
|
70
|
+
|
|
71
|
+
if given.present?
|
|
72
|
+
reader.first_name = given
|
|
73
|
+
reader.last_name = family
|
|
74
|
+
return
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
split = Names.split_display_name(reader.display_name)
|
|
78
|
+
reader.first_name = split ? split[:first_name] : reader.display_name.to_s.split.first
|
|
79
|
+
reader.last_name = split ? split[:last_name] : nil
|
|
80
|
+
end
|
|
81
|
+
private_class_method :assign_names_from
|
|
82
|
+
|
|
52
83
|
# Find or create the reader behind a verified email address. Uses the same
|
|
53
84
|
# provider/uid identity columns as OmniAuth, with provider "email", so an
|
|
54
85
|
# email reader is indistinguishable from any other downstream.
|
|
@@ -137,6 +168,16 @@ module Bible270
|
|
|
137
168
|
update(bible_version: Translations.normalize(code))
|
|
138
169
|
end
|
|
139
170
|
|
|
171
|
+
def update_passage_source(source)
|
|
172
|
+
source = source.to_s
|
|
173
|
+
return false unless PASSAGE_SOURCES.include?(source)
|
|
174
|
+
|
|
175
|
+
update(passage_source: source)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def bible_gateway? = passage_source == 'bible_gateway'
|
|
179
|
+
def blue_letter_bible? = passage_source == 'blue_letter_bible'
|
|
180
|
+
|
|
140
181
|
# Readers are listed by first name, matching how they are shown — the display
|
|
141
182
|
# name is "First Last", so sorting on it needs only case folding. Surname
|
|
142
183
|
# order was inconsistent with the community page and read oddly next to names
|
|
@@ -47,4 +47,31 @@
|
|
|
47
47
|
<p style="color:var(--faint)">No readers yet.</p>
|
|
48
48
|
<% end %>
|
|
49
49
|
|
|
50
|
+
<div class="b270-panel">
|
|
51
|
+
<h2 class="b270-subhead">Write to everyone</h2>
|
|
52
|
+
|
|
53
|
+
<% if @reachable.zero? %>
|
|
54
|
+
<p class="b270-hint" style="margin-top:0">No reader has an email address yet.</p>
|
|
55
|
+
<% else %>
|
|
56
|
+
<p class="b270-hint" style="margin-top:0">
|
|
57
|
+
Goes to <strong><%= @reachable %></strong>
|
|
58
|
+
<%= @reachable == 1 ? 'reader' : 'readers' %> individually, not as one message with
|
|
59
|
+
everyone in bcc — so each is greeted by name.
|
|
60
|
+
<% if @last_broadcast_at.present? %>
|
|
61
|
+
<br>Last sent <%= b270_datetime(@last_broadcast_at) %><%
|
|
62
|
+
%><% if @last_broadcast_subject.present? %>: <em><%= @last_broadcast_subject %></em><% end %>.
|
|
63
|
+
<% end %>
|
|
64
|
+
</p>
|
|
65
|
+
|
|
66
|
+
<%= form_with url: admin_broadcast_path, method: :post, class: "b270-form",
|
|
67
|
+
data: { turbo_confirm: "Send this to #{@reachable} #{@reachable == 1 ? 'reader' : 'readers'}?" } do %>
|
|
68
|
+
<%= text_field_tag :subject, params[:subject], placeholder: "Subject", class: "b270-input" %>
|
|
69
|
+
<%= text_area_tag :body, params[:body], placeholder: "What would you like to say?", rows: 6 %>
|
|
70
|
+
<div class="row">
|
|
71
|
+
<%= submit_tag "Send", class: "b270-btn" %>
|
|
72
|
+
</div>
|
|
73
|
+
<% end %>
|
|
74
|
+
<% end %>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
50
77
|
<p style="margin-top:18px"><%= link_to '← Back to the plan', root_path %></p>
|
|
@@ -92,6 +92,29 @@
|
|
|
92
92
|
<%= render "bible270/shared/reflections", comments: @comments, moderate: true %>
|
|
93
93
|
</div>
|
|
94
94
|
|
|
95
|
+
<div class="b270-panel">
|
|
96
|
+
<h2 class="b270-subhead">Which translation they read</h2>
|
|
97
|
+
<p class="b270-hint" style="margin-top:0">
|
|
98
|
+
Reading links open in this translation.
|
|
99
|
+
<% if @reader.bible_version.blank? %>
|
|
100
|
+
They have expressed no preference, so they follow the site default
|
|
101
|
+
(<%= Bible270.config.bible_version %>).
|
|
102
|
+
<% end %>
|
|
103
|
+
</p>
|
|
104
|
+
|
|
105
|
+
<%= form_with url: admin_reader_version_path(@reader), method: :patch, class: "b270-form" do %>
|
|
106
|
+
<div class="row">
|
|
107
|
+
<%= select_tag :bible_version,
|
|
108
|
+
options_for_select([["Site default (#{Bible270.config.bible_version})", ""]] +
|
|
109
|
+
Bible270::Translations.options,
|
|
110
|
+
@reader.bible_version.to_s),
|
|
111
|
+
class: "b270-select", style: "width:auto" %>
|
|
112
|
+
</div>
|
|
113
|
+
<%= render "bible270/shared/passage_source", reader: @reader %>
|
|
114
|
+
<%= submit_tag "Save", class: "b270-btn" %>
|
|
115
|
+
<% end %>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
95
118
|
<div class="b270-panel">
|
|
96
119
|
<h2 class="b270-subhead">Remove</h2>
|
|
97
120
|
<p class="b270-hint" style="margin-top:0">Deletes the reader together with every check-off and
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
<% end %>
|
|
36
36
|
</ul>
|
|
37
37
|
<div class="b270-parts-meta">
|
|
38
|
-
<%= read_parts.size %> of <%= parts.size %> chapters <%= b270_version_tag %>
|
|
38
|
+
<%= read_parts.size %> of <%= parts.size %> chapters <%= b270_version_tag(track: track) %>
|
|
39
39
|
</div>
|
|
40
40
|
</div>
|
|
41
41
|
<% else %>
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
<% end %>
|
|
51
51
|
<div class="b270-rbody">
|
|
52
52
|
<div class="b270-cat"><%= t[:label] %></div>
|
|
53
|
-
<div class="b270-ref"><%= link_to ref, b270_passage_url(ref), target: "_blank", rel: "noopener" %> <%= b270_version_tag %></div>
|
|
53
|
+
<div class="b270-ref"><%= link_to ref, b270_passage_url(ref), target: "_blank", rel: "noopener" %> <%= b270_version_tag(track: track) %></div>
|
|
54
54
|
</div>
|
|
55
55
|
<% end %>
|
|
56
56
|
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<body style="margin:0;padding:24px;background:#ece6da;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#231f18">
|
|
4
|
+
<div style="max-width:520px;margin:0 auto;background:#f4efe6;border:1px solid #d7cfbf;border-radius:14px;padding:28px">
|
|
5
|
+
<p style="margin:0 0 16px;font-size:15px">
|
|
6
|
+
<%= @reader.first_name.presence || @reader.display_name %>,
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<div style="font-size:15px;line-height:1.6"><%= simple_format(@body) %></div>
|
|
10
|
+
|
|
11
|
+
<p style="margin:24px 0 0;padding-top:16px;border-top:1px solid #d7cfbf;font-size:12px;color:#9a9082">
|
|
12
|
+
<%= @app_name %><% if @plan_url %> ·
|
|
13
|
+
<a href="<%= @plan_url %>" style="color:#a5812c"><%= @plan_url %></a><% end %>
|
|
14
|
+
</p>
|
|
15
|
+
</div>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
class: "b270-select" %>
|
|
40
40
|
<span class="b270-hint" style="margin:0">Reading links open in this translation.</span>
|
|
41
41
|
</label>
|
|
42
|
+
<%= render "bible270/shared/passage_source", reader: current_reader %>
|
|
42
43
|
|
|
43
44
|
<%= submit_tag "Save", class: "b270-btn" %>
|
|
44
45
|
<% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<% original_languages = reader.effective_bible_version == Bible270::Translations::ORIGINAL_LANGUAGES %>
|
|
2
|
+
<div class="b270-source-choices" role="radiogroup" aria-label="Reading link source">
|
|
3
|
+
<label class="b270-source-choice <%= 'disabled' if original_languages %>">
|
|
4
|
+
<%= radio_button_tag :passage_source, "bible_gateway",
|
|
5
|
+
!original_languages && reader.bible_gateway?, disabled: original_languages %>
|
|
6
|
+
<span>Bible Gateway</span>
|
|
7
|
+
</label>
|
|
8
|
+
<label class="b270-source-choice <%= 'disabled' if original_languages %>">
|
|
9
|
+
<%= radio_button_tag :passage_source, "blue_letter_bible",
|
|
10
|
+
original_languages || reader.blue_letter_bible?, disabled: original_languages %>
|
|
11
|
+
<span>Blue Letter Bible</span>
|
|
12
|
+
</label>
|
|
13
|
+
</div>
|
|
@@ -134,6 +134,15 @@
|
|
|
134
134
|
.b270-parts-meta{margin-top:10px;font-size:12.5px;color:var(--faint)}
|
|
135
135
|
.b270-version{font-size:11.5px;color:var(--faint);font-weight:400;letter-spacing:.02em;white-space:nowrap}
|
|
136
136
|
.b270-select{font:inherit;font-size:14px;padding:10px 30px 10px 12px;border:1px solid var(--line);border-radius:9px;background:#fff;color:var(--ink);width:100%;max-width:100%;text-overflow:ellipsis}
|
|
137
|
+
.b270-source-choices{display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin:8px 0 2px;color:var(--muted);font-size:12.5px}
|
|
138
|
+
.b270-source-choice{display:inline-flex;align-items:center;gap:7px;cursor:pointer;white-space:nowrap}
|
|
139
|
+
.b270-source-choice input{appearance:none;width:17px;height:17px;margin:0;border:1px solid var(--line);border-radius:3px;background:#fff;display:grid;place-content:center;cursor:pointer}
|
|
140
|
+
.b270-source-choice input::before{content:"";width:9px;height:9px;transform:scale(0);transition:transform .1s;clip-path:polygon(14% 44%,0 59%,38% 100%,100% 18%,82% 0,37% 65%);background:var(--paper)}
|
|
141
|
+
.b270-source-choice input:checked{background:var(--gold);border-color:var(--gold)}
|
|
142
|
+
.b270-source-choice input:checked::before{transform:scale(1)}
|
|
143
|
+
.b270-source-choice input:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
|
|
144
|
+
.b270-source-choice.disabled{cursor:default;opacity:.55}
|
|
145
|
+
.b270-source-choice input:disabled{cursor:default}
|
|
137
146
|
.b270-ref a{text-decoration:none;border-bottom:1px solid transparent}
|
|
138
147
|
.b270-ref a:hover{border-bottom-color:var(--gold)}
|
|
139
148
|
.b270-reading.done .b270-ref{color:var(--muted)}
|
|
@@ -183,6 +192,7 @@
|
|
|
183
192
|
.b270-comment.b270-editing{background:rgba(165,129,44,.05);border-radius:10px;padding:12px}
|
|
184
193
|
.b270-editing textarea{min-height:64px}
|
|
185
194
|
.b270-cdel{font-size:12px;color:var(--oxblood);background:none;border:none;cursor:pointer;padding:0;margin-left:8px}
|
|
195
|
+
.b270-input{width:100%;padding:9px 11px;border:1px solid var(--line);border-radius:9px;background:#fff;font:inherit;font-size:14px;margin-bottom:8px}
|
|
186
196
|
.b270-form textarea{width:100%;font:inherit;font-size:15px;border:1px solid var(--line);border-radius:10px;padding:11px 13px;background:#fff;resize:vertical;min-height:76px}
|
|
187
197
|
.b270-form .row{display:flex;align-items:center;gap:10px;margin-top:10px}
|
|
188
198
|
.b270 .b270-btn{border:none;border-radius:10px;padding:8px 16px;margin-top:4px;font:inherit;font-weight:600;font-size:14px;cursor:pointer;background:var(--ink);color:var(--paper);text-decoration:none;display:inline-block}
|
data/config/routes.rb
CHANGED
|
@@ -22,10 +22,12 @@ Bible270::Engine.routes.draw do
|
|
|
22
22
|
delete 'admin/readers/:id', to: 'admin#destroy'
|
|
23
23
|
patch 'admin/readers/:id/profile', to: 'admin#update_profile', as: :admin_reader_profile
|
|
24
24
|
delete 'admin/readers/:id/avatar', to: 'admin#remove_avatar', as: :admin_reader_avatar
|
|
25
|
+
patch 'admin/readers/:id/version', to: 'admin#update_bible_version', as: :admin_reader_version
|
|
25
26
|
patch 'admin/readers/:id/start', to: 'admin#update_start', as: :admin_reader_start
|
|
26
27
|
patch 'admin/readers/:id/through', to: 'admin#complete_through', as: :admin_reader_through
|
|
27
28
|
post 'admin/readers/:id/days/:day', to: 'admin#toggle_day', as: :admin_reader_day
|
|
28
29
|
|
|
30
|
+
post 'admin/broadcast', to: 'admin#broadcast', as: :admin_broadcast
|
|
29
31
|
patch 'admin/enrollment', to: 'admin#update_enrollment', as: :admin_enrollment
|
|
30
32
|
get 'admin/comments', to: 'admin#comments', as: :admin_comments
|
|
31
33
|
patch 'admin/comments/:id/hide', to: 'admin#hide_comment', as: :admin_hide_comment
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddPassageSourceToBible270Readers < ActiveRecord::Migration[7.0]
|
|
4
|
+
def up
|
|
5
|
+
add_column :bible270_readers, :passage_source, :string, default: 'bible_gateway', null: false
|
|
6
|
+
|
|
7
|
+
return unless column_exists?(:bible270_readers, :blue_letter_bible)
|
|
8
|
+
|
|
9
|
+
execute <<~SQL.squish
|
|
10
|
+
UPDATE bible270_readers
|
|
11
|
+
SET passage_source = 'blue_letter_bible'
|
|
12
|
+
WHERE blue_letter_bible = TRUE
|
|
13
|
+
SQL
|
|
14
|
+
remove_column :bible270_readers, :blue_letter_bible
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def down
|
|
18
|
+
add_column :bible270_readers, :blue_letter_bible, :boolean, default: false, null: false
|
|
19
|
+
execute <<~SQL.squish
|
|
20
|
+
UPDATE bible270_readers
|
|
21
|
+
SET blue_letter_bible = TRUE
|
|
22
|
+
WHERE passage_source = 'blue_letter_bible'
|
|
23
|
+
SQL
|
|
24
|
+
remove_column :bible270_readers, :passage_source
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -347,12 +347,7 @@ module Bible270
|
|
|
347
347
|
@after_sign_out_path = nil
|
|
348
348
|
@bible_version = 'NKJV'
|
|
349
349
|
@bible_versions = Translations::VERSIONS.keys
|
|
350
|
-
@passage_url_builder = ->(reference, version)
|
|
351
|
-
search = URI.encode_www_form_component(reference)
|
|
352
|
-
# Translations.gateway_code maps 'NASB95' to Bible Gateway's 'NASB1995'.
|
|
353
|
-
version = URI.encode_www_form_component(Translations.gateway_code(version))
|
|
354
|
-
"https://www.biblegateway.com/passage/?search=#{search}&version=#{version}"
|
|
355
|
-
end
|
|
350
|
+
@passage_url_builder = ->(reference, version) { Translations.passage_url(reference, version) }
|
|
356
351
|
@app_name = 'Daily Bread'
|
|
357
352
|
@tagline = 'Journeying through Scripture in 9 months'
|
|
358
353
|
@time_zone = nil
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bible270
|
|
4
|
+
# Repairs schema_migrations after a database is copied without matching migration
|
|
5
|
+
# bookkeeping. A migration is safe to mark only when every table, column and
|
|
6
|
+
# current index it contributes is already present.
|
|
7
|
+
class MigrationReconciler
|
|
8
|
+
Status = Struct.new(:migration, :missing, keyword_init: true) do
|
|
9
|
+
def complete? = missing.empty?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
CHECKS = {
|
|
13
|
+
'CreateBible270Readers' => :check_readers,
|
|
14
|
+
'CreateBible270Checkoffs' => :check_checkoffs,
|
|
15
|
+
'CreateBible270Comments' => :check_comments,
|
|
16
|
+
'CreateBible270SignInTokens' => :check_sign_in_tokens,
|
|
17
|
+
'AddNamesToBible270Readers' => :check_names,
|
|
18
|
+
'AddApprovalToBible270Comments' => :check_approval,
|
|
19
|
+
'CreateBible270Settings' => :check_settings,
|
|
20
|
+
'AddBibleVersionToBible270Readers' => :check_bible_version,
|
|
21
|
+
'AddPartToBible270Checkoffs' => :check_checkoff_parts,
|
|
22
|
+
'AddRememberTokenToBible270Readers' => :check_remember_token,
|
|
23
|
+
'AddMentionNoticesToBible270Readers' => :check_mention_notices,
|
|
24
|
+
'AddParentToBible270Comments' => :check_comment_parent,
|
|
25
|
+
'CreateBible270Likes' => :check_likes,
|
|
26
|
+
'AddBlueLetterBibleToBible270Readers' => :check_blue_letter_bible,
|
|
27
|
+
'AddPassageSourceToBible270Readers' => :check_passage_source,
|
|
28
|
+
'CreateActiveStorageTables' => :check_active_storage
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
def self.for_current_database
|
|
32
|
+
context = ActiveRecord::MigrationContext.new(ActiveRecord::Migrator.migrations_paths)
|
|
33
|
+
new(connection: ActiveRecord::Base.connection, migration_context: context)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def initialize(connection:, migration_context:)
|
|
37
|
+
@connection = connection
|
|
38
|
+
@migration_context = migration_context
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def statuses
|
|
42
|
+
applied = migration_context.get_all_versions.map(&:to_i)
|
|
43
|
+
migration_context.migrations.filter_map do |migration|
|
|
44
|
+
check = CHECKS[migration.name]
|
|
45
|
+
next unless check
|
|
46
|
+
next if applied.include?(migration.version.to_i)
|
|
47
|
+
|
|
48
|
+
Status.new(migration: migration, missing: public_send(check))
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def mark_applied!(status)
|
|
53
|
+
raise ArgumentError, "#{status.migration.name} is not reflected in the schema" unless status.complete?
|
|
54
|
+
|
|
55
|
+
migration_context.schema_migration.create_version(status.migration.version.to_s)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def check_readers
|
|
59
|
+
table_missing(
|
|
60
|
+
:bible270_readers,
|
|
61
|
+
columns: %i[id display_name email avatar_url provider uid owner_type owner_id started_on created_at updated_at],
|
|
62
|
+
indexes: [
|
|
63
|
+
{ columns: %i[owner_type owner_id] },
|
|
64
|
+
{ columns: %i[provider uid], unique: true }
|
|
65
|
+
]
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def check_checkoffs
|
|
70
|
+
table_missing(
|
|
71
|
+
:bible270_checkoffs,
|
|
72
|
+
columns: %i[id reader_id day track created_at updated_at],
|
|
73
|
+
indexes: [{ columns: %i[reader_id] }, { columns: %i[day] }],
|
|
74
|
+
foreign_keys: [{ column: :reader_id, to_table: :bible270_readers }]
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def check_comments
|
|
79
|
+
table_missing(
|
|
80
|
+
:bible270_comments,
|
|
81
|
+
columns: %i[id reader_id day track body created_at updated_at],
|
|
82
|
+
indexes: [{ columns: %i[reader_id] }, { columns: %i[day created_at] }],
|
|
83
|
+
foreign_keys: [{ column: :reader_id, to_table: :bible270_readers }]
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def check_sign_in_tokens
|
|
88
|
+
table_missing(
|
|
89
|
+
:bible270_sign_in_tokens,
|
|
90
|
+
columns: %i[id email token_digest display_name expires_at consumed_at created_at updated_at],
|
|
91
|
+
indexes: [
|
|
92
|
+
{ columns: %i[token_digest], unique: true },
|
|
93
|
+
{ columns: %i[email created_at] }
|
|
94
|
+
]
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def check_names
|
|
99
|
+
table_missing(
|
|
100
|
+
:bible270_readers,
|
|
101
|
+
columns: %i[first_name last_name],
|
|
102
|
+
indexes: [{ columns: %i[last_name first_name] }]
|
|
103
|
+
) + table_missing(:bible270_sign_in_tokens, columns: %i[first_name last_name])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def check_approval
|
|
107
|
+
table_missing(
|
|
108
|
+
:bible270_comments,
|
|
109
|
+
columns: %i[approved moderated_at],
|
|
110
|
+
indexes: [{ columns: %i[approved day] }]
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def check_settings
|
|
115
|
+
table_missing(
|
|
116
|
+
:bible270_settings,
|
|
117
|
+
columns: %i[id key value created_at updated_at],
|
|
118
|
+
indexes: [{ columns: %i[key], unique: true }]
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def check_bible_version
|
|
123
|
+
table_missing(:bible270_readers, columns: %i[bible_version])
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def check_checkoff_parts
|
|
127
|
+
table_missing(
|
|
128
|
+
:bible270_checkoffs,
|
|
129
|
+
columns: %i[part],
|
|
130
|
+
indexes: [{ columns: %i[reader_id day track part], unique: true }]
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def check_remember_token
|
|
135
|
+
table_missing(
|
|
136
|
+
:bible270_readers,
|
|
137
|
+
columns: %i[remember_token],
|
|
138
|
+
indexes: [{ columns: %i[remember_token] }]
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def check_mention_notices
|
|
143
|
+
table_missing(:bible270_readers, columns: %i[notify_on_mention])
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def check_comment_parent
|
|
147
|
+
table_missing(
|
|
148
|
+
:bible270_comments,
|
|
149
|
+
columns: %i[parent_id],
|
|
150
|
+
indexes: [{ columns: %i[parent_id] }],
|
|
151
|
+
foreign_keys: [{ column: :parent_id, to_table: :bible270_comments }]
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def check_likes
|
|
156
|
+
table_missing(
|
|
157
|
+
:bible270_likes,
|
|
158
|
+
columns: %i[id reader_id comment_id created_at updated_at],
|
|
159
|
+
indexes: [
|
|
160
|
+
{ columns: %i[reader_id comment_id], unique: true },
|
|
161
|
+
{ columns: %i[comment_id] }
|
|
162
|
+
],
|
|
163
|
+
foreign_keys: [
|
|
164
|
+
{ column: :reader_id, to_table: :bible270_readers },
|
|
165
|
+
{ column: :comment_id, to_table: :bible270_comments }
|
|
166
|
+
]
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def check_blue_letter_bible
|
|
171
|
+
return [] if connection.column_exists?(:bible270_readers, :passage_source)
|
|
172
|
+
|
|
173
|
+
table_missing(:bible270_readers, columns: %i[blue_letter_bible])
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def check_passage_source
|
|
177
|
+
table_missing(:bible270_readers, columns: %i[passage_source])
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# The Bible270 installer can install Active Storage for reader avatars. A
|
|
181
|
+
# copied database can therefore have the same timestamp mismatch for Rails'
|
|
182
|
+
# generated Active Storage migration as it has for the engine migrations.
|
|
183
|
+
def check_active_storage
|
|
184
|
+
table_missing(
|
|
185
|
+
:active_storage_blobs,
|
|
186
|
+
columns: %i[id key filename content_type metadata service_name byte_size checksum created_at],
|
|
187
|
+
indexes: [{ columns: %i[key], unique: true }]
|
|
188
|
+
) + table_missing(
|
|
189
|
+
:active_storage_attachments,
|
|
190
|
+
columns: %i[id name record_type record_id blob_id created_at],
|
|
191
|
+
indexes: [
|
|
192
|
+
{ columns: %i[blob_id] },
|
|
193
|
+
{ columns: %i[record_type record_id name blob_id], unique: true }
|
|
194
|
+
],
|
|
195
|
+
foreign_keys: [{ column: :blob_id, to_table: :active_storage_blobs }]
|
|
196
|
+
) + table_missing(
|
|
197
|
+
:active_storage_variant_records,
|
|
198
|
+
columns: %i[id blob_id variation_digest],
|
|
199
|
+
indexes: [{ columns: %i[blob_id variation_digest], unique: true }],
|
|
200
|
+
foreign_keys: [{ column: :blob_id, to_table: :active_storage_blobs }]
|
|
201
|
+
)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
private
|
|
205
|
+
|
|
206
|
+
attr_reader :connection, :migration_context
|
|
207
|
+
|
|
208
|
+
def table_missing(table, columns:, indexes: [], foreign_keys: [])
|
|
209
|
+
return ["table #{table}"] unless connection.data_source_exists?(table)
|
|
210
|
+
|
|
211
|
+
missing_columns(table, columns) +
|
|
212
|
+
missing_indexes(table, indexes) +
|
|
213
|
+
missing_foreign_keys(table, foreign_keys)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def missing_columns(table, expected_columns)
|
|
217
|
+
existing = connection.columns(table).map { |column| column.name.to_s }
|
|
218
|
+
expected_columns.filter_map do |column|
|
|
219
|
+
"column #{table}.#{column}" unless existing.include?(column.to_s)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def missing_indexes(table, expected_indexes)
|
|
224
|
+
existing = connection.indexes(table)
|
|
225
|
+
expected_indexes.filter_map do |expected|
|
|
226
|
+
columns = expected.fetch(:columns).map(&:to_s)
|
|
227
|
+
next if existing.any? { |index| matching_index?(index, columns, expected) }
|
|
228
|
+
|
|
229
|
+
qualifier = expected[:unique] ? 'unique index' : 'index'
|
|
230
|
+
"#{qualifier} #{table}(#{columns.join(', ')})"
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def matching_index?(index, columns, expected)
|
|
235
|
+
index.columns == columns && (!expected.key?(:unique) || index.unique == expected[:unique])
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def missing_foreign_keys(table, expected_foreign_keys)
|
|
239
|
+
existing = connection.foreign_keys(table)
|
|
240
|
+
expected_foreign_keys.filter_map do |expected|
|
|
241
|
+
next if existing.any? { |foreign_key| matching_foreign_key?(foreign_key, expected) }
|
|
242
|
+
|
|
243
|
+
"foreign key #{table}.#{expected[:column]} -> #{expected[:to_table]}"
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def matching_foreign_key?(foreign_key, expected)
|
|
248
|
+
foreign_key.column.to_s == expected.fetch(:column).to_s &&
|
|
249
|
+
foreign_key.to_table.to_s == expected.fetch(:to_table).to_s
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'bible270/versification'
|
|
5
|
+
|
|
3
6
|
module Bible270
|
|
4
7
|
# The translations a reader may choose between, and how each maps onto the
|
|
5
8
|
# external reader's own code. Kept free of Rails so it can be unit tested.
|
|
@@ -11,14 +14,22 @@ module Bible270
|
|
|
11
14
|
# `label` is the full name, for prose. `short` is what goes in the select,
|
|
12
15
|
# where "New American Standard Bible 1995" overflows the box.
|
|
13
16
|
VERSIONS = {
|
|
14
|
-
'NKJV' => { label: 'New King James Version', short: 'New King James Version', gateway: 'NKJV' },
|
|
15
|
-
'NASB95' => { label: 'New American Standard Bible 1995', short: 'New American Standard 1995', gateway: 'NASB1995' },
|
|
16
|
-
'LSB' => { label: 'Legacy Standard Bible', short: 'Legacy Standard Version', gateway: 'LSB' },
|
|
17
|
-
'ESV' => { label: 'English Standard Version', short: 'English Standard Version', gateway: 'ESV' },
|
|
18
|
-
'KJV' => { label: 'King James Version', short: 'King James Version', gateway: 'KJV' }
|
|
17
|
+
'NKJV' => { label: 'New King James Version', short: 'New King James Version', gateway: 'NKJV', blue_letter: 'nkjv' },
|
|
18
|
+
'NASB95' => { label: 'New American Standard Bible 1995', short: 'New American Standard 1995', gateway: 'NASB1995', blue_letter: 'nasb95' },
|
|
19
|
+
'LSB' => { label: 'Legacy Standard Bible', short: 'Legacy Standard Version', gateway: 'LSB', blue_letter: 'lsb' },
|
|
20
|
+
'ESV' => { label: 'English Standard Version', short: 'English Standard Version', gateway: 'ESV', blue_letter: 'esv' },
|
|
21
|
+
'KJV' => { label: 'King James Version', short: 'King James Version', gateway: 'KJV', blue_letter: 'kjv' },
|
|
22
|
+
'HEB/GRK' => { label: 'Hebrew and Greek', short: 'Hebrew and Greek', gateway: 'WLC', blue_letter: 'wlc/mgnt' }
|
|
19
23
|
}.freeze
|
|
20
24
|
|
|
21
25
|
DEFAULT = 'NKJV'
|
|
26
|
+
ORIGINAL_LANGUAGES = 'HEB/GRK'
|
|
27
|
+
BLUE_LETTER_BASE_URL = 'https://www.blueletterbible.org'
|
|
28
|
+
BLUE_LETTER_BOOK_CODES = %w[
|
|
29
|
+
gen exo lev num deu jos jdg rut 1sa 2sa 1ki 2ki 1ch 2ch ezr neh est job psa pro ecc sng
|
|
30
|
+
isa jer lam eze dan hos joe amo oba jon mic nah hab zep hag zec mal mat mar luk joh act rom
|
|
31
|
+
1co 2co gal eph php col 1th 2th 1ti 2ti tit phm heb jas 1pe 2pe 1jo 2jo 3jo jud rev
|
|
32
|
+
].freeze
|
|
22
33
|
|
|
23
34
|
module_function
|
|
24
35
|
|
|
@@ -57,6 +68,51 @@ module Bible270
|
|
|
57
68
|
codes.map { |code| ["#{code} — #{short_label(code)}", code] }
|
|
58
69
|
end
|
|
59
70
|
|
|
71
|
+
def passage_url(reference, version, blue_letter: false)
|
|
72
|
+
if blue_letter || normalize(version) == ORIGINAL_LANGUAGES
|
|
73
|
+
return blue_letter_url(reference, version)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
search = URI.encode_www_form_component(reference)
|
|
77
|
+
gateway = URI.encode_www_form_component(gateway_code(version))
|
|
78
|
+
"https://www.biblegateway.com/passage/?search=#{search}&version=#{gateway}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Blue Letter Bible identifies a verse with its canonical chapter number times
|
|
82
|
+
# 1,000 plus the verse number: Genesis 1:1 is 1001 and Matthew 1:1 is 930001.
|
|
83
|
+
# A plan reference can span chapters or books; BLB has no equivalent passage
|
|
84
|
+
# query URL, so link to the first chapter/verse and let its chapter navigation
|
|
85
|
+
# carry the reader onward.
|
|
86
|
+
def blue_letter_url(reference, version = ORIGINAL_LANGUAGES)
|
|
87
|
+
books = Versification::VERSES.keys
|
|
88
|
+
book_index = books.index { |book| reference.to_s.start_with?("#{book} ") }
|
|
89
|
+
return "#{BLUE_LETTER_BASE_URL}/" unless book_index
|
|
90
|
+
|
|
91
|
+
book = books[book_index]
|
|
92
|
+
location = reference.to_s.delete_prefix("#{book} ")
|
|
93
|
+
match = location.match(%r{\A(\d+)(?::(\d+))?})
|
|
94
|
+
return "#{BLUE_LETTER_BASE_URL}/" unless match
|
|
95
|
+
|
|
96
|
+
chapter = match[1].to_i
|
|
97
|
+
verse = match[2]&.to_i || 1
|
|
98
|
+
chapter_verses = Versification::VERSES[book]
|
|
99
|
+
return "#{BLUE_LETTER_BASE_URL}/" unless chapter.between?(1, chapter_verses.length)
|
|
100
|
+
return "#{BLUE_LETTER_BASE_URL}/" unless verse.between?(1, chapter_verses[chapter - 1])
|
|
101
|
+
|
|
102
|
+
canonical_chapter = books.first(book_index).sum { |prior| Versification::VERSES[prior].length } + chapter
|
|
103
|
+
new_testament = book_index >= books.index('Matthew')
|
|
104
|
+
reader = if normalize(version) == ORIGINAL_LANGUAGES
|
|
105
|
+
new_testament ? 'mgnt' : 'wlc'
|
|
106
|
+
else
|
|
107
|
+
VERSIONS.dig(normalize(version), :blue_letter)
|
|
108
|
+
end
|
|
109
|
+
return "#{BLUE_LETTER_BASE_URL}/" if reader.to_s.empty?
|
|
110
|
+
|
|
111
|
+
anchor = (canonical_chapter * 1000) + verse
|
|
112
|
+
"#{BLUE_LETTER_BASE_URL}/#{reader}/#{BLUE_LETTER_BOOK_CODES[book_index]}/" \
|
|
113
|
+
"#{chapter}/#{verse}/s_#{anchor}"
|
|
114
|
+
end
|
|
115
|
+
|
|
60
116
|
# Falls back to the configured default, then to ESV, so a link is never built
|
|
61
117
|
# with a blank version.
|
|
62
118
|
def resolve(code)
|
data/lib/bible270/version.rb
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bible270/migration_reconciler'
|
|
4
|
+
|
|
5
|
+
namespace :bible270 do
|
|
6
|
+
namespace :migrations do
|
|
7
|
+
desc 'Reconcile copied Bible270/Active Storage tables with schema_migrations (set APPLY=1 to write)'
|
|
8
|
+
task reconcile: :environment do
|
|
9
|
+
reconciler = Bible270::MigrationReconciler.for_current_database
|
|
10
|
+
statuses = reconciler.statuses
|
|
11
|
+
|
|
12
|
+
if statuses.empty?
|
|
13
|
+
puts 'No pending Bible270 or related Active Storage migrations were found.'
|
|
14
|
+
next
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
complete, incomplete = statuses.partition(&:complete?)
|
|
18
|
+
|
|
19
|
+
puts 'Already reflected in the database schema:'
|
|
20
|
+
if complete.empty?
|
|
21
|
+
puts ' (none)'
|
|
22
|
+
else
|
|
23
|
+
complete.each do |status|
|
|
24
|
+
puts " #{status.migration.version} #{status.migration.name}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
unless incomplete.empty?
|
|
29
|
+
puts
|
|
30
|
+
puts 'Still missing schema changes (these will remain pending):'
|
|
31
|
+
incomplete.each do |status|
|
|
32
|
+
puts " #{status.migration.version} #{status.migration.name}"
|
|
33
|
+
status.missing.each { |item| puts " - #{item}" }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
puts
|
|
38
|
+
unless ENV['APPLY'] == '1'
|
|
39
|
+
puts 'Preview only; schema_migrations was not changed.'
|
|
40
|
+
puts 'Review the list, back up the database, then rerun with APPLY=1.'
|
|
41
|
+
next
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if complete.empty?
|
|
45
|
+
puts 'Nothing was safe to mark as applied.'
|
|
46
|
+
next
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
ActiveRecord::Base.transaction do
|
|
50
|
+
complete.each { |status| reconciler.mark_applied!(status) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
puts "Marked #{complete.size} verified #{'migration'.pluralize(complete.size)} as applied."
|
|
54
|
+
puts 'Run bin/rails db:migrate to apply the migrations whose schema changes are still missing.'
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bible270
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.18.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew vonderLuft
|
|
@@ -91,6 +91,8 @@ files:
|
|
|
91
91
|
- app/controllers/bible270/sessions_controller.rb
|
|
92
92
|
- app/helpers/bible270/plan_helper.rb
|
|
93
93
|
- app/mailers/bible270/application_mailer.rb
|
|
94
|
+
- app/mailers/bible270/broadcast.html.erb
|
|
95
|
+
- app/mailers/bible270/broadcast.text.erb
|
|
94
96
|
- app/mailers/bible270/notice_mailer.rb
|
|
95
97
|
- app/mailers/bible270/sign_in_mailer.rb
|
|
96
98
|
- app/models/bible270/application_record.rb
|
|
@@ -119,6 +121,8 @@ files:
|
|
|
119
121
|
- app/views/bible270/days/show.html.erb
|
|
120
122
|
- app/views/bible270/likes/_likes.html.erb
|
|
121
123
|
- app/views/bible270/likes/toggle.turbo_stream.erb
|
|
124
|
+
- app/views/bible270/notice_mailer/broadcast.html.erb
|
|
125
|
+
- app/views/bible270/notice_mailer/broadcast.text.erb
|
|
122
126
|
- app/views/bible270/notice_mailer/mentioned.html.erb
|
|
123
127
|
- app/views/bible270/notice_mailer/mentioned.text.erb
|
|
124
128
|
- app/views/bible270/notice_mailer/new_reader.html.erb
|
|
@@ -132,6 +136,7 @@ files:
|
|
|
132
136
|
- app/views/bible270/shared/_footer.html.erb
|
|
133
137
|
- app/views/bible270/shared/_grid_legend.html.erb
|
|
134
138
|
- app/views/bible270/shared/_header.html.erb
|
|
139
|
+
- app/views/bible270/shared/_passage_source.html.erb
|
|
135
140
|
- app/views/bible270/shared/_progress.html.erb
|
|
136
141
|
- app/views/bible270/shared/_reflections.html.erb
|
|
137
142
|
- app/views/bible270/shared/_styles.html.erb
|
|
@@ -153,6 +158,8 @@ files:
|
|
|
153
158
|
- db/migrate/20260101000011_add_mention_notices_to_bible270_readers.rb
|
|
154
159
|
- db/migrate/20260101000012_add_parent_to_bible270_comments.rb
|
|
155
160
|
- db/migrate/20260101000013_create_bible270_likes.rb
|
|
161
|
+
- db/migrate/20260101000014_add_blue_letter_bible_to_bible270_readers.rb
|
|
162
|
+
- db/migrate/20260101000015_add_passage_source_to_bible270_readers.rb
|
|
156
163
|
- lib/bible270.rb
|
|
157
164
|
- lib/bible270/avatars.rb
|
|
158
165
|
- lib/bible270/configuration.rb
|
|
@@ -160,6 +167,7 @@ files:
|
|
|
160
167
|
- lib/bible270/engine.rb
|
|
161
168
|
- lib/bible270/favicon.rb
|
|
162
169
|
- lib/bible270/mentions.rb
|
|
170
|
+
- lib/bible270/migration_reconciler.rb
|
|
163
171
|
- lib/bible270/names.rb
|
|
164
172
|
- lib/bible270/plan.rb
|
|
165
173
|
- lib/bible270/translations.rb
|
|
@@ -168,6 +176,7 @@ files:
|
|
|
168
176
|
- lib/generators/bible270/install/install_generator.rb
|
|
169
177
|
- lib/generators/bible270/install/templates/bible270.rb.tt
|
|
170
178
|
- lib/generators/bible270/install/templates/omniauth.rb.tt
|
|
179
|
+
- lib/tasks/bible270_migrations.rake
|
|
171
180
|
homepage: https://github.com/avonderluft/bible270
|
|
172
181
|
licenses:
|
|
173
182
|
- MIT
|