monkrb 0.17.0 → 0.19.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 +133 -0
- data/README.md +6 -5
- data/exe/monk +45 -7
- data/lib/monk/jobs/adapters/pg.rb +272 -0
- data/lib/monk/jobs/args.rb +43 -0
- data/lib/monk/jobs/claim.rb +7 -0
- data/lib/monk/jobs/errors.rb +29 -0
- data/lib/monk/jobs/job.rb +117 -0
- data/lib/monk/jobs/runtime.rb +239 -0
- data/lib/monk/jobs/worker.rb +94 -0
- data/lib/monk/jobs.rb +229 -0
- data/lib/monk/live/client/monk_live.js +2 -2
- data/lib/monk/live/client/protocol.js +5 -0
- data/lib/monk/live.rb +10 -0
- data/lib/monk/mail/errors.rb +7 -0
- data/lib/monk/mail/later.rb +53 -0
- data/lib/monk/persistence/pg.rb +21 -1
- data/lib/monk/scaffold.rb +369 -70
- data/lib/monk/templates/auth/app/mailers/app_mailer.rb +22 -0
- data/lib/monk/templates/auth/config/auth.rb +7 -20
- data/lib/monk/templates/base/app/app.rb +9 -0
- data/lib/monk/templates/base/app/broadcasts/.keep +0 -0
- data/lib/monk/templates/base/app/helpers/.keep +0 -0
- data/lib/monk/templates/base/app/jobs/.keep +0 -0
- data/lib/monk/templates/base/app/mailers/.keep +0 -0
- data/lib/monk/templates/base/app/models/.keep +0 -0
- data/lib/monk/templates/base/app/presenters/.keep +0 -0
- data/lib/monk/templates/base/bin/websocket_server +6 -0
- data/lib/monk/templates/base/config/load.rb +18 -0
- data/lib/monk/templates/base/config.ru +3 -12
- data/lib/monk/templates/base/public/js/app.js +1 -1
- data/lib/monk/templates/jobs/app/jobs/hello_job.rb +9 -0
- data/lib/monk/templates/jobs/app/jobs/send_login_link.rb +28 -0
- data/lib/monk/templates/jobs/bin/jobs +18 -0
- data/lib/monk/templates/jobs/config/jobs.rb +15 -0
- data/lib/monk/templates/jobs/db/migrate/00000000000002_create_jobs_tables.down.sql +3 -0
- data/lib/monk/templates/jobs/db/migrate/00000000000002_create_jobs_tables.up.sql +48 -0
- data/lib/monk/templates/live/{config.ru → app/app.rb} +1 -6
- data/lib/monk/templates/live/{views → app/views}/index.erb +2 -2
- data/lib/monk/templates/live/bin/websocket_server +14 -2
- data/lib/monk/templates/live/config/live.rb +1 -1
- data/lib/monk/templates/live/config/live_pg.rb +1 -1
- data/lib/monk/templates/mail/config/mail.rb +2 -2
- data/lib/monk/templates/postgres/Dockerfile +3 -1
- data/lib/monk/templates/postgres/bin/console +4 -2
- data/lib/monk/version.rb +1 -1
- data/lib/monk/websocket/errors.rb +11 -0
- data/lib/monk/websocket/listeners.rb +23 -0
- data/lib/monk/websocket/pg_fanout.rb +55 -20
- data/lib/monk/websocket/redis_fanout.rb +57 -14
- data/lib/monk/websocket/registry.rb +6 -0
- data/lib/monk/websocket/server.rb +77 -17
- metadata +32 -7
- /data/lib/monk/templates/auth/{views → app/views}/mail/magic_link.erb +0 -0
- /data/lib/monk/templates/base/{views → app/views}/index.erb +0 -0
- /data/lib/monk/templates/base/{views → app/views}/layouts/app.erb +0 -0
- /data/lib/monk/templates/live/{views → app/views}/live/_hits.erb +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
require "redis"
|
|
2
2
|
require "securerandom"
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
require_relative "listeners"
|
|
3
5
|
|
|
4
6
|
module Monk
|
|
5
7
|
module WebSocket
|
|
@@ -9,7 +11,7 @@ module Monk
|
|
|
9
11
|
# LISTEN/NOTIFY per docs/design/websocket.md Open Question 3).
|
|
10
12
|
#
|
|
11
13
|
# Wraps a Registry with the same public interface (#register,
|
|
12
|
-
# #unregister, #count, #broadcast), so an app swaps which object it
|
|
14
|
+
# #unregister, #count, #broadcast, #listen!), so an app swaps which object it
|
|
13
15
|
# holds and nothing else changes. #broadcast still delivers to this
|
|
14
16
|
# process's local Registry directly -- same latency and reliability as
|
|
15
17
|
# a plain Registry if Redis is briefly unavailable -- and additionally
|
|
@@ -41,13 +43,43 @@ module Monk
|
|
|
41
43
|
@redis_url = redis_url.dup.freeze
|
|
42
44
|
@origin = SecureRandom.uuid.freeze
|
|
43
45
|
|
|
46
|
+
# No live connection lives on this object -- #listen! runs its
|
|
47
|
+
# subscriber in a Ractor of its own, and #publisher below opens one
|
|
48
|
+
# client per Ractor, lazily -- so there's nothing left unshareable
|
|
49
|
+
# and this instance can freeze itself the same way Registry does.
|
|
50
|
+
# That matters for real use: an app holds this behind a module
|
|
51
|
+
# constant (e.g. ChatServer::REGISTRY, mirroring Registry's own
|
|
52
|
+
# existing usage) read from every connection's own Ractor, which
|
|
53
|
+
# raises Ractor::IsolationError unless the constant's value is
|
|
54
|
+
# shareable.
|
|
55
|
+
freeze
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Starts relaying other processes' broadcasts into this process's
|
|
59
|
+
# registry: a subscriber Ractor with its own Redis connection. Only
|
|
60
|
+
# the process that holds sockets calls this (bin/websocket_server, at
|
|
61
|
+
# boot, from the main Ractor) -- a publish-only process (bin/server,
|
|
62
|
+
# bin/jobs) never does, so it holds no subscriber connection at all.
|
|
63
|
+
# Returns once the psubscribe is in effect, so nothing broadcast
|
|
64
|
+
# after it can be missed; raises ListenError if Redis can't be
|
|
65
|
+
# reached. Calling it again is a no-op.
|
|
66
|
+
def listen!
|
|
67
|
+
return self if Listeners.listening?(@origin)
|
|
68
|
+
|
|
69
|
+
ready = Ractor::Port.new
|
|
44
70
|
# Ractor.new's block args must be shareable: registry is (Registry
|
|
45
71
|
# freezes itself around a Ractor, which is inherently shareable),
|
|
46
|
-
#
|
|
47
|
-
# itself is built inside the block, never passed in --
|
|
48
|
-
# PG::Connection's per-Ractor pattern (persistence/pg.rb).
|
|
49
|
-
|
|
72
|
+
# the URL and origin are frozen Strings, a Port always is. The
|
|
73
|
+
# Redis client itself is built inside the block, never passed in --
|
|
74
|
+
# mirrors PG::Connection's per-Ractor pattern (persistence/pg.rb).
|
|
75
|
+
Ractor.new(@registry, @redis_url, @origin, ready) do |registry, url, origin, ready|
|
|
76
|
+
subscribed = false
|
|
50
77
|
Redis.new(url: url).psubscribe("#{Monk::WebSocket::RedisFanout::CHANNEL_PREFIX}*") do |on|
|
|
78
|
+
on.psubscribe do
|
|
79
|
+
subscribed = true
|
|
80
|
+
ready.send(:ok)
|
|
81
|
+
end
|
|
82
|
+
|
|
51
83
|
on.pmessage do |_pattern, channel, envelope|
|
|
52
84
|
sender, payload = envelope.split("\0", 2)
|
|
53
85
|
next if sender == origin
|
|
@@ -66,19 +98,30 @@ module Monk
|
|
|
66
98
|
registry.broadcast(key, payload)
|
|
67
99
|
end
|
|
68
100
|
end
|
|
101
|
+
rescue StandardError => e
|
|
102
|
+
raise if subscribed
|
|
103
|
+
|
|
104
|
+
ready.send("#{e.class}: #{e.message}")
|
|
69
105
|
end
|
|
70
106
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
107
|
+
result = ready.receive
|
|
108
|
+
raise ListenError, "Monk::WebSocket::RedisFanout#listen! couldn't subscribe: #{result}" unless result == :ok
|
|
109
|
+
|
|
110
|
+
Listeners.add(@origin)
|
|
111
|
+
self
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def register(key, port)
|
|
115
|
+
unless Listeners.listening?(@origin)
|
|
116
|
+
raise NotListeningError,
|
|
117
|
+
"Monk::WebSocket::RedisFanout#register before #listen!: this socket would never get " \
|
|
118
|
+
"other processes' broadcasts -- call listen! once at boot in the process that holds " \
|
|
119
|
+
"sockets (bin/websocket_server: Monk::Live.listen!)"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
@registry.register(key, port)
|
|
79
123
|
end
|
|
80
124
|
|
|
81
|
-
def register(key, port) = @registry.register(key, port)
|
|
82
125
|
def unregister(key, port) = @registry.unregister(key, port)
|
|
83
126
|
def count(key) = @registry.count(key)
|
|
84
127
|
|
|
@@ -67,6 +67,12 @@ module Monk
|
|
|
67
67
|
ask(:count, key, nil)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
# Nothing to listen to: every broadcast reaching this registry comes
|
|
71
|
+
# from this process. Here so a WS process calls #listen! the same
|
|
72
|
+
# way whether it holds a Registry or a RedisFanout/PgFanout around
|
|
73
|
+
# one.
|
|
74
|
+
def listen! = self
|
|
75
|
+
|
|
70
76
|
private
|
|
71
77
|
|
|
72
78
|
def ask(op, key, arg)
|
|
@@ -25,9 +25,20 @@ module Monk
|
|
|
25
25
|
# docs/history/chat-gap-analysis.md) -- authenticate: true only checks the
|
|
26
26
|
# credential once, at the handshake, so a session revoked or
|
|
27
27
|
# expired afterward would otherwise leave the socket live
|
|
28
|
-
# indefinitely. Requires authenticate: true (there's no
|
|
29
|
-
# to reverify without it) and defaults to nil (disabled),
|
|
30
|
-
# ping_interval:.
|
|
28
|
+
# indefinitely. Requires authenticate: true or :optional (there's no
|
|
29
|
+
# credential to reverify without it) and defaults to nil (disabled),
|
|
30
|
+
# same as ping_interval:. Under :optional it watches only the
|
|
31
|
+
# connections that have a session.
|
|
32
|
+
#
|
|
33
|
+
# authenticate: :optional is the middle mode (docs/design/websocket.md,
|
|
34
|
+
# "Anonymous connections"): a connection with a valid session gets its
|
|
35
|
+
# subject, and one without -- no credential, or a revoked or expired
|
|
36
|
+
# one -- comes in anonymous (subject nil) instead of being refused.
|
|
37
|
+
# What an anonymous connection may then do is the handler's call:
|
|
38
|
+
# Monk::Live's rules deny it everything a rule doesn't open with
|
|
39
|
+
# `anonymous: true`. The Origin check still applies: to a cookie, as
|
|
40
|
+
# under true, and to an anonymous connection that sends an Origin,
|
|
41
|
+
# since only a browser does.
|
|
31
42
|
#
|
|
32
43
|
# max_payload_size: (bytes) -- see Connection::DEFAULT_MAX_PAYLOAD_SIZE
|
|
33
44
|
# for what it guards against (gap 5). Unlike ping_interval:/
|
|
@@ -35,6 +46,11 @@ module Monk
|
|
|
35
46
|
# hostile client on a public endpoint, not an opt-in behavior
|
|
36
47
|
# change, so a server that doesn't mention it still gets the
|
|
37
48
|
# default cap rather than no cap at all.
|
|
49
|
+
AUTHENTICATE_MODES = [false, true, :optional].freeze
|
|
50
|
+
|
|
51
|
+
# The signals #run treats as "stop": Ctrl+C and TERM.
|
|
52
|
+
STOP_SIGNALS = [Signal.list.fetch("INT"), Signal.list.fetch("TERM")].freeze
|
|
53
|
+
|
|
38
54
|
def initialize(
|
|
39
55
|
port:, bind: "0.0.0.0", allowed_origins: nil, authenticate: false, ping_interval: nil,
|
|
40
56
|
reverify_interval: nil, max_payload_size: Connection::DEFAULT_MAX_PAYLOAD_SIZE
|
|
@@ -43,11 +59,15 @@ module Monk
|
|
|
43
59
|
raise ArgumentError, "ping_interval must be positive, got #{ping_interval.inspect}"
|
|
44
60
|
end
|
|
45
61
|
|
|
62
|
+
unless AUTHENTICATE_MODES.include?(authenticate)
|
|
63
|
+
raise ArgumentError, "authenticate must be false, true or :optional, got #{authenticate.inspect}"
|
|
64
|
+
end
|
|
65
|
+
|
|
46
66
|
if reverify_interval
|
|
47
67
|
unless reverify_interval.positive?
|
|
48
68
|
raise ArgumentError, "reverify_interval must be positive, got #{reverify_interval.inspect}"
|
|
49
69
|
end
|
|
50
|
-
raise ArgumentError, "reverify_interval requires authenticate: true" unless authenticate
|
|
70
|
+
raise ArgumentError, "reverify_interval requires authenticate: true or :optional" unless authenticate
|
|
51
71
|
end
|
|
52
72
|
|
|
53
73
|
unless max_payload_size.positive?
|
|
@@ -57,8 +77,8 @@ module Monk
|
|
|
57
77
|
if authenticate
|
|
58
78
|
unless defined?(Monk::Auth) && Monk::Auth.config
|
|
59
79
|
raise Monk::AuthNotConfiguredError,
|
|
60
|
-
"Monk::WebSocket::Server.new(authenticate:
|
|
61
|
-
"configured -- call Monk::Auth.configure first"
|
|
80
|
+
"Monk::WebSocket::Server.new(authenticate: #{authenticate.inspect}) requires Monk::Auth to " \
|
|
81
|
+
"already be configured -- call Monk::Auth.configure first"
|
|
62
82
|
end
|
|
63
83
|
|
|
64
84
|
# Monk::Auth's config is a plain, unfrozen Hash until this runs
|
|
@@ -107,15 +127,22 @@ module Monk
|
|
|
107
127
|
end
|
|
108
128
|
ractor.send(socket, move: true)
|
|
109
129
|
end
|
|
110
|
-
rescue
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
130
|
+
rescue SignalException => e
|
|
131
|
+
raise unless STOP_SIGNALS.include?(e.signo)
|
|
132
|
+
|
|
133
|
+
# Ctrl+C (INT, raised as Interrupt) and TERM (what docker stop,
|
|
134
|
+
# Kubernetes, Fly, Render and systemd send on every deploy) both
|
|
135
|
+
# land here: Ruby raises them in the thread blocked in
|
|
136
|
+
# TCPServer#accept. Unrescued, INT prints a backtrace and TERM ends
|
|
137
|
+
# the process by the signal (exit 143) -- either reads as a crash
|
|
138
|
+
# though it's the normal way to stop a long-running server. Here
|
|
139
|
+
# the server stops accepting and #run returns, so the script ends
|
|
140
|
+
# with exit 0, as bin/server and bin/jobs do. Open connections are
|
|
141
|
+
# dropped when the process exits, with no close frame
|
|
142
|
+
# (docs/design/websocket.md, "Stopping the server").
|
|
143
|
+
# Registry#ask's Ractor::ClosedError rescue (registry.rb) guards a
|
|
144
|
+
# different failure point in this same shutdown -- a live
|
|
145
|
+
# connection's cleanup racing the registry Ractor's teardown.
|
|
119
146
|
@tcp_server.close
|
|
120
147
|
end
|
|
121
148
|
|
|
@@ -138,9 +165,14 @@ module Monk
|
|
|
138
165
|
end
|
|
139
166
|
|
|
140
167
|
subject = nil
|
|
141
|
-
|
|
168
|
+
case authenticate
|
|
169
|
+
when true
|
|
142
170
|
subject = authenticate!(socket, headers, allowed_origins)
|
|
143
171
|
return unless subject
|
|
172
|
+
when :optional
|
|
173
|
+
return unless origin_allowed_for_optional?(socket, headers, allowed_origins)
|
|
174
|
+
|
|
175
|
+
subject = identify(headers)
|
|
144
176
|
end
|
|
145
177
|
|
|
146
178
|
socket.write(Handshake.response_for(request))
|
|
@@ -148,7 +180,7 @@ module Monk
|
|
|
148
180
|
connection = Connection.new(socket, subject: subject, max_payload_size: max_payload_size)
|
|
149
181
|
connection.start_heartbeat(ping_interval) if ping_interval
|
|
150
182
|
reverify_thread =
|
|
151
|
-
if reverify_interval
|
|
183
|
+
if reverify_interval && subject
|
|
152
184
|
start_reverify(connection, Handshake.credential_from(headers)[:token], reverify_interval)
|
|
153
185
|
end
|
|
154
186
|
begin
|
|
@@ -194,6 +226,34 @@ module Monk
|
|
|
194
226
|
end
|
|
195
227
|
private_class_method :authenticate!
|
|
196
228
|
|
|
229
|
+
# authenticate: :optional's Origin check, before anything is verified.
|
|
230
|
+
# A Bearer connection is exempt, as under true. A cookie needs an
|
|
231
|
+
# allowed Origin, as under true. An anonymous connection that sends an
|
|
232
|
+
# Origin needs an allowed one too: only a browser sends it, and this
|
|
233
|
+
# stops another site's page from opening sockets here on its
|
|
234
|
+
# visitors' behalf; one that sends none (not a browser) gets in.
|
|
235
|
+
# Writes the 403 itself and returns false when refused.
|
|
236
|
+
def self.origin_allowed_for_optional?(socket, headers, allowed_origins)
|
|
237
|
+
return true unless allowed_origins
|
|
238
|
+
|
|
239
|
+
credential = Handshake.credential_from(headers)
|
|
240
|
+
return true if credential&.fetch(:via) == :bearer
|
|
241
|
+
|
|
242
|
+
origin = headers["origin"]
|
|
243
|
+
allowed = credential ? origin && allowed_origins.include?(origin) : origin.nil? || allowed_origins.include?(origin)
|
|
244
|
+
socket.write("HTTP/1.1 403 Forbidden\r\n\r\n") unless allowed
|
|
245
|
+
allowed
|
|
246
|
+
end
|
|
247
|
+
private_class_method :origin_allowed_for_optional?
|
|
248
|
+
|
|
249
|
+
# The subject of a valid session, or nil -- no credential, or one
|
|
250
|
+
# Monk::Auth no longer accepts, both mean anonymous under :optional.
|
|
251
|
+
def self.identify(headers)
|
|
252
|
+
credential = Handshake.credential_from(headers)
|
|
253
|
+
credential && Monk::Auth.verify(credential[:token])
|
|
254
|
+
end
|
|
255
|
+
private_class_method :identify
|
|
256
|
+
|
|
197
257
|
# Sleeps in `interval`-sized steps, re-running Monk::Auth.verify
|
|
198
258
|
# against the same raw credential each time, until it comes back
|
|
199
259
|
# nil (revoked or expired) -- then closes the connection with the
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: monkrb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matteo Folin
|
|
@@ -176,6 +176,14 @@ files:
|
|
|
176
176
|
- lib/monk/environment.rb
|
|
177
177
|
- lib/monk/errors.rb
|
|
178
178
|
- lib/monk/freeze_hooks.rb
|
|
179
|
+
- lib/monk/jobs.rb
|
|
180
|
+
- lib/monk/jobs/adapters/pg.rb
|
|
181
|
+
- lib/monk/jobs/args.rb
|
|
182
|
+
- lib/monk/jobs/claim.rb
|
|
183
|
+
- lib/monk/jobs/errors.rb
|
|
184
|
+
- lib/monk/jobs/job.rb
|
|
185
|
+
- lib/monk/jobs/runtime.rb
|
|
186
|
+
- lib/monk/jobs/worker.rb
|
|
179
187
|
- lib/monk/live.rb
|
|
180
188
|
- lib/monk/live/client/idiomorph.LICENSE
|
|
181
189
|
- lib/monk/live/client/idiomorph.js
|
|
@@ -192,6 +200,7 @@ files:
|
|
|
192
200
|
- lib/monk/mail.rb
|
|
193
201
|
- lib/monk/mail/address.rb
|
|
194
202
|
- lib/monk/mail/errors.rb
|
|
203
|
+
- lib/monk/mail/later.rb
|
|
195
204
|
- lib/monk/mail/message.rb
|
|
196
205
|
- lib/monk/mail/mime.rb
|
|
197
206
|
- lib/monk/mail/transports.rb
|
|
@@ -206,29 +215,44 @@ files:
|
|
|
206
215
|
- lib/monk/scaffold.rb
|
|
207
216
|
- lib/monk/settings.rb
|
|
208
217
|
- lib/monk/state_ractor.rb
|
|
218
|
+
- lib/monk/templates/auth/app/mailers/app_mailer.rb
|
|
219
|
+
- lib/monk/templates/auth/app/views/mail/magic_link.erb
|
|
209
220
|
- lib/monk/templates/auth/config/auth.rb
|
|
210
221
|
- lib/monk/templates/auth/db/migrate/00000000000001_create_auth_tables.down.sql
|
|
211
222
|
- lib/monk/templates/auth/db/migrate/00000000000001_create_auth_tables.up.sql
|
|
212
|
-
- lib/monk/templates/auth/views/mail/magic_link.erb
|
|
213
223
|
- lib/monk/templates/base/.dockerignore
|
|
214
224
|
- lib/monk/templates/base/.gitignore
|
|
215
225
|
- lib/monk/templates/base/.ruby-version
|
|
216
226
|
- lib/monk/templates/base/Dockerfile
|
|
217
227
|
- lib/monk/templates/base/Gemfile
|
|
228
|
+
- lib/monk/templates/base/app/app.rb
|
|
229
|
+
- lib/monk/templates/base/app/broadcasts/.keep
|
|
230
|
+
- lib/monk/templates/base/app/helpers/.keep
|
|
231
|
+
- lib/monk/templates/base/app/jobs/.keep
|
|
232
|
+
- lib/monk/templates/base/app/mailers/.keep
|
|
233
|
+
- lib/monk/templates/base/app/models/.keep
|
|
234
|
+
- lib/monk/templates/base/app/presenters/.keep
|
|
235
|
+
- lib/monk/templates/base/app/views/index.erb
|
|
236
|
+
- lib/monk/templates/base/app/views/layouts/app.erb
|
|
218
237
|
- lib/monk/templates/base/bin/server
|
|
219
238
|
- lib/monk/templates/base/bin/websocket_server
|
|
220
239
|
- lib/monk/templates/base/config.ru
|
|
240
|
+
- lib/monk/templates/base/config/load.rb
|
|
221
241
|
- lib/monk/templates/base/config/settings.rb
|
|
222
242
|
- lib/monk/templates/base/public/css/app.css
|
|
223
243
|
- lib/monk/templates/base/public/js/app.js
|
|
224
|
-
- lib/monk/templates/
|
|
225
|
-
- lib/monk/templates/
|
|
244
|
+
- lib/monk/templates/jobs/app/jobs/hello_job.rb
|
|
245
|
+
- lib/monk/templates/jobs/app/jobs/send_login_link.rb
|
|
246
|
+
- lib/monk/templates/jobs/bin/jobs
|
|
247
|
+
- lib/monk/templates/jobs/config/jobs.rb
|
|
248
|
+
- lib/monk/templates/jobs/db/migrate/00000000000002_create_jobs_tables.down.sql
|
|
249
|
+
- lib/monk/templates/jobs/db/migrate/00000000000002_create_jobs_tables.up.sql
|
|
250
|
+
- lib/monk/templates/live/app/app.rb
|
|
251
|
+
- lib/monk/templates/live/app/views/index.erb
|
|
252
|
+
- lib/monk/templates/live/app/views/live/_hits.erb
|
|
226
253
|
- lib/monk/templates/live/bin/websocket_server
|
|
227
|
-
- lib/monk/templates/live/config.ru
|
|
228
254
|
- lib/monk/templates/live/config/live.rb
|
|
229
255
|
- lib/monk/templates/live/config/live_pg.rb
|
|
230
|
-
- lib/monk/templates/live/views/index.erb
|
|
231
|
-
- lib/monk/templates/live/views/live/_hits.erb
|
|
232
256
|
- lib/monk/templates/mail/Gemfile.extra
|
|
233
257
|
- lib/monk/templates/mail/config/mail.rb
|
|
234
258
|
- lib/monk/templates/postgres/Dockerfile
|
|
@@ -245,6 +269,7 @@ files:
|
|
|
245
269
|
- lib/monk/websocket/errors.rb
|
|
246
270
|
- lib/monk/websocket/frame.rb
|
|
247
271
|
- lib/monk/websocket/handshake.rb
|
|
272
|
+
- lib/monk/websocket/listeners.rb
|
|
248
273
|
- lib/monk/websocket/pg_fanout.rb
|
|
249
274
|
- lib/monk/websocket/redis_fanout.rb
|
|
250
275
|
- lib/monk/websocket/registry.rb
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|