ask-guests 0.2.0 → 0.3.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 +25 -0
- data/README.md +37 -4
- data/db/migrate/20260917120000_create_ask_guest_sessions.rb +30 -0
- data/lib/ask/guests/active_record/session_base.rb +9 -1
- data/lib/ask/guests/active_record/session_store.rb +14 -1
- data/lib/ask/guests/errors.rb +19 -0
- data/lib/ask/guests/version.rb +1 -1
- data/lib/generators/ask/guests/install/install_generator.rb +81 -22
- data/lib/generators/ask/guests/install/templates/guest_session.rb +16 -0
- data/lib/generators/ask/guests/install/templates/initializer.rb +36 -32
- metadata +17 -2
- data/lib/generators/ask/guests/install/templates/migration.rb +0 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8bec805817f573f8d9fcbc2b5e7bc7c0e6622aed391b4af6d61f02c3544f1c10
|
|
4
|
+
data.tar.gz: 381594c955f847741546177fc5bb03ead0e71ed6baa62f5adc98277e736fa223
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f97bf4428e04bfcb3fb018b76663b825ed46357419ec43ecd5bc2b3c5fe18463fcc8d9f42ecb6d26be59a43404f3d5fa04b1ee2c715983fa971b09c08318d1c5
|
|
7
|
+
data.tar.gz: cdc1be08b4f0ef9c13e93625c16be13fb35198fbd0ed2b9d222fc76cde0989b1fc456804aee2bf8a5cc7016ad3cf4c5304e74833638ed9f898b5a54704aa9c6b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0] — 2026-09-17
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **The table ships with the gem.** `db/migrate/create_ask_guest_sessions.rb` is
|
|
8
|
+
now the one definition of `ask_guest_sessions`, and the install generator copies
|
|
9
|
+
it into the host (`migration_template`, so it lands with a fresh timestamp
|
|
10
|
+
and is skipped if the host already has one). Re-running the installer after
|
|
11
|
+
an upgrade installs any migration a later version adds.
|
|
12
|
+
- **The installer writes the whole app-side wiring:** the migration, the
|
|
13
|
+
`GuestSession` model, and `config/initializers/ask_guests.rb` — then prints
|
|
14
|
+
the controller, claim, and sweep steps. Nothing is left for the host to
|
|
15
|
+
transcribe from a README.
|
|
16
|
+
- **`Ask::Guests::MissingTable`** — the ActiveRecord store checks for its table
|
|
17
|
+
on the first read or write (`#verify_schema!`) and raises this, naming the
|
|
18
|
+
file to copy, instead of surfacing a database error about a relation.
|
|
19
|
+
- The README documents the non-Rails path (plain ActiveRecord/Sinatra) and why
|
|
20
|
+
the host owns the schema rather than the gem creating it at boot.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- `Ask::Guests::ActiveRecord::SessionStore` raises on a missing table for
|
|
25
|
+
`#create`, `#find`, and `#sweep_stale`; `#verify_schema!` is public if a host
|
|
26
|
+
wants to check at boot.
|
|
27
|
+
|
|
3
28
|
## [0.2.0] — 2026-09-16
|
|
4
29
|
|
|
5
30
|
### Changed
|
data/README.md
CHANGED
|
@@ -31,14 +31,47 @@ require "ask-guests/rails" # controller concern, auth hook, sweep job
|
|
|
31
31
|
gem "ask-guests"
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
### Rails
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
bin/rails generate ask:guests:install
|
|
38
|
+
bin/rails db:migrate
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
The installer copies the `ask_guest_sessions` migration out of the gem, writes
|
|
42
|
+
`app/models/guest_session.rb`, creates `config/initializers/ask_guests.rb`, and
|
|
43
|
+
prints the controller wiring. Then register what claiming transfers (see
|
|
44
|
+
[Claim](#claim)) and schedule the sweep (see [Retention](#retention)).
|
|
45
|
+
|
|
46
|
+
The table ships with the gem (`db/migrate/create_ask_guest_sessions.rb`) rather
|
|
47
|
+
than being created by it. Your app owns its schema: migrations run at deploy
|
|
48
|
+
time, under review, against whichever database you point them at — a gem that
|
|
49
|
+
creates tables at boot would need DDL rights at runtime, race other servers,
|
|
50
|
+
and have to guess which database is yours. Re-running the installer after
|
|
51
|
+
upgrading installs any migration a later version added, and skips the ones you
|
|
52
|
+
already have.
|
|
53
|
+
|
|
54
|
+
### Anywhere else (plain ActiveRecord, Sinatra, a script)
|
|
55
|
+
|
|
56
|
+
The core needs nothing; the ActiveRecord adapter needs the table:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
require "ask-guests/active_record"
|
|
60
|
+
|
|
61
|
+
class GuestSession < Ask::Guests::ActiveRecord::SessionBase
|
|
62
|
+
self.table_name = "ask_guest_sessions"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Ask::Guests.configure do |config|
|
|
66
|
+
config.secret = ENV.fetch("GUEST_TOKEN_SECRET")
|
|
67
|
+
config.store = Ask::Guests::ActiveRecord::SessionStore.new(model: GuestSession)
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Copy `db/migrate/create_ask_guest_sessions.rb` from the gem into your migrations
|
|
72
|
+
and run them however you migrate. If the table is missing, the store raises
|
|
73
|
+
`Ask::Guests::MissingTable` at the first read or write — with the file to copy
|
|
74
|
+
in the message — rather than letting the database complain about a relation.
|
|
42
75
|
|
|
43
76
|
## Core usage
|
|
44
77
|
|
|
@@ -128,7 +161,7 @@ Ask::Guests.sweep.call # destroys unclaimed sessions inactive past retention
|
|
|
128
161
|
```ruby
|
|
129
162
|
# app/models/guest_session.rb
|
|
130
163
|
class GuestSession < Ask::Guests::ActiveRecord::SessionBase
|
|
131
|
-
self.table_name = "
|
|
164
|
+
self.table_name = "ask_guest_sessions"
|
|
132
165
|
has_many :work_requests, foreign_key: :guest_session_id, dependent: :destroy
|
|
133
166
|
end
|
|
134
167
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# The ask_guest_sessions table.
|
|
2
|
+
#
|
|
3
|
+
# Shipped with the gem so the table has one definition: the install generator
|
|
4
|
+
# copies this file into a Rails app, and an app without Rails copies the same
|
|
5
|
+
# body into whatever migrates its database. A host that edits its copy keeps
|
|
6
|
+
# its own table — but the columns the store reads and writes are these.
|
|
7
|
+
#
|
|
8
|
+
# Named for the gem, like every other table this ecosystem ships: a host may
|
|
9
|
+
# well have its own `guest_sessions`, and the gem has no business claiming it.
|
|
10
|
+
#
|
|
11
|
+
# `t.json` rather than `jsonb` so the same migration runs on PostgreSQL,
|
|
12
|
+
# MySQL, and SQLite; nothing here queries inside the documents, so the jsonb
|
|
13
|
+
# operators buy nothing. Stamped 7.1 because that is the oldest Active Record
|
|
14
|
+
# the gem supports — a newer Rails runs it unchanged.
|
|
15
|
+
class CreateAskGuestSessions < ActiveRecord::Migration[7.1]
|
|
16
|
+
def change
|
|
17
|
+
create_table :ask_guest_sessions do |t|
|
|
18
|
+
t.json :counters, null: false, default: {}
|
|
19
|
+
t.json :metadata, null: false, default: {}
|
|
20
|
+
t.datetime :last_seen_at
|
|
21
|
+
t.datetime :converted_at
|
|
22
|
+
t.references :owner, polymorphic: true
|
|
23
|
+
t.timestamps
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Speculative: sessions at risk of being swept.
|
|
27
|
+
add_index :ask_guest_sessions, :converted_at
|
|
28
|
+
add_index :ask_guest_sessions, :last_seen_at
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -8,7 +8,7 @@ module Ask
|
|
|
8
8
|
# table created by the migration:
|
|
9
9
|
#
|
|
10
10
|
# class GuestSession < Ask::Guests::ActiveRecord::SessionBase
|
|
11
|
-
# self.table_name = "
|
|
11
|
+
# self.table_name = "ask_guest_sessions"
|
|
12
12
|
# has_many :work_requests, foreign_key: :guest_session_id, dependent: :destroy
|
|
13
13
|
# end
|
|
14
14
|
#
|
|
@@ -22,6 +22,14 @@ module Ask
|
|
|
22
22
|
class SessionBase < ::ActiveRecord::Base
|
|
23
23
|
self.abstract_class = true
|
|
24
24
|
|
|
25
|
+
# The table is named for the gem, the way the rest of the ecosystem's
|
|
26
|
+
# tables are: a host may have its own `guest_sessions`. A subclass that
|
|
27
|
+
# says nothing lands on ask_guest_sessions rather than claiming the
|
|
28
|
+
# generic name; one that sets table_name itself keeps its own.
|
|
29
|
+
def self.table_name_prefix
|
|
30
|
+
"ask_"
|
|
31
|
+
end
|
|
32
|
+
|
|
25
33
|
# The gem owns these columns' semantics; use the adapter-independent
|
|
26
34
|
# JSON type so PostgreSQL json/jsonb and SQLite text columns behave
|
|
27
35
|
# identically (and the model loads without a database connection).
|
|
@@ -6,7 +6,7 @@ module Ask
|
|
|
6
6
|
# ActiveRecord-backed session store.
|
|
7
7
|
#
|
|
8
8
|
# class GuestSession < Ask::Guests::ActiveRecord::SessionBase
|
|
9
|
-
# self.table_name = "
|
|
9
|
+
# self.table_name = "ask_guest_sessions"
|
|
10
10
|
# has_many :work_requests, foreign_key: :guest_session_id, dependent: :destroy
|
|
11
11
|
# end
|
|
12
12
|
#
|
|
@@ -26,6 +26,7 @@ module Ask
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def create(attributes = {})
|
|
29
|
+
verify_schema!
|
|
29
30
|
attributes = attributes.to_h.transform_keys(&:to_sym)
|
|
30
31
|
now = Guests.configuration.now
|
|
31
32
|
record = model.new(
|
|
@@ -42,6 +43,7 @@ module Ask
|
|
|
42
43
|
def find(id)
|
|
43
44
|
return nil if id.nil?
|
|
44
45
|
|
|
46
|
+
verify_schema!
|
|
45
47
|
model.find_by(id: id)&.to_guest_session
|
|
46
48
|
end
|
|
47
49
|
|
|
@@ -69,6 +71,7 @@ module Ask
|
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
def sweep_stale(before:)
|
|
74
|
+
verify_schema!
|
|
72
75
|
destroyed = 0
|
|
73
76
|
stale_scope(before).find_each do |record|
|
|
74
77
|
record.destroy
|
|
@@ -77,6 +80,16 @@ module Ask
|
|
|
77
80
|
destroyed
|
|
78
81
|
end
|
|
79
82
|
|
|
83
|
+
# Raises MissingTable unless the sessions table is there. Called by the
|
|
84
|
+
# operations a host reaches first, so a forgotten migration names
|
|
85
|
+
# itself instead of surfacing as a database error about a relation.
|
|
86
|
+
# Idempotent and cheap: Active Record caches the schema.
|
|
87
|
+
def verify_schema!
|
|
88
|
+
return true if model.table_exists?
|
|
89
|
+
|
|
90
|
+
raise MissingTable, model.table_name
|
|
91
|
+
end
|
|
92
|
+
|
|
80
93
|
def transaction(&block)
|
|
81
94
|
model.transaction(&block)
|
|
82
95
|
end
|
data/lib/ask/guests/errors.rb
CHANGED
|
@@ -11,5 +11,24 @@ module Ask
|
|
|
11
11
|
super
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
|
+
|
|
15
|
+
# Raised on the first store operation when the sessions table is not
|
|
16
|
+
# there. The gem ships the table, so a host that has not installed it
|
|
17
|
+
# should hear that — and the command that fixes it — rather than a
|
|
18
|
+
# database complaining about a relation nobody mentioned.
|
|
19
|
+
class MissingTable < Error
|
|
20
|
+
def initialize(table_name = "ask_guest_sessions")
|
|
21
|
+
super(<<~MESSAGE.strip)
|
|
22
|
+
The #{table_name} table does not exist.
|
|
23
|
+
|
|
24
|
+
Install the table the gem ships, then migrate:
|
|
25
|
+
bin/rails generate ask:guests:install
|
|
26
|
+
bin/rails db:migrate
|
|
27
|
+
|
|
28
|
+
Without Rails, copy db/migrate/*_create_ask_guest_sessions.rb from the gem
|
|
29
|
+
into your migrations and run them.
|
|
30
|
+
MESSAGE
|
|
31
|
+
end
|
|
32
|
+
end
|
|
14
33
|
end
|
|
15
34
|
end
|
data/lib/ask/guests/version.rb
CHANGED
|
@@ -1,65 +1,124 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rails/generators"
|
|
4
|
+
require "rails/generators/migration"
|
|
4
5
|
|
|
5
6
|
module Ask
|
|
6
7
|
module Guests
|
|
7
8
|
module Generators
|
|
9
|
+
# Installs ask-guests into a Rails app: the ask_guest_sessions migration, the
|
|
10
|
+
# model that maps it, and an initializer to configure. Run it once, then
|
|
11
|
+
# edit what it wrote.
|
|
12
|
+
#
|
|
8
13
|
# bin/rails generate ask:guests:install
|
|
14
|
+
#
|
|
15
|
+
# The migrations are copied from the gem's own db/migrate, so the table
|
|
16
|
+
# has one definition — the one the store expects. A host that already
|
|
17
|
+
# migrated ask_guest_sessions keeps its file: an identical name is skipped
|
|
18
|
+
# rather than duplicated. Re-running the installer after an upgrade is
|
|
19
|
+
# how a host picks up a migration a later version added.
|
|
9
20
|
class InstallGenerator < ::Rails::Generators::Base
|
|
21
|
+
include Rails::Generators::Migration
|
|
22
|
+
|
|
10
23
|
source_root File.expand_path("templates", __dir__)
|
|
24
|
+
# The DDL lives in the gem's db/migrate, not in a second copy here.
|
|
25
|
+
source_paths << File.expand_path("../../../../../db/migrate", __dir__)
|
|
26
|
+
|
|
27
|
+
desc "Creates the ask_guest_sessions migration, the model, and the ask-guests initializer"
|
|
28
|
+
|
|
29
|
+
def self.next_migration_number(_dir)
|
|
30
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The migrations the gem ships, by file name.
|
|
34
|
+
def self.migrations
|
|
35
|
+
Dir[File.expand_path("../../../../../db/migrate/*.rb", __dir__)]
|
|
36
|
+
.map { |path| File.basename(path) }.sort
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def create_migrations
|
|
40
|
+
self.class.migrations.each do |migration|
|
|
41
|
+
next if installed?(migration) && !options[:force]
|
|
11
42
|
|
|
12
|
-
|
|
43
|
+
migration_template migration, "db/migrate/#{migration.sub(/\A\d+_/, "")}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def create_model
|
|
48
|
+
template "guest_session.rb", "app/models/guest_session.rb"
|
|
49
|
+
end
|
|
13
50
|
|
|
14
51
|
def create_initializer
|
|
15
52
|
template "initializer.rb", "config/initializers/ask_guests.rb"
|
|
16
53
|
end
|
|
17
54
|
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
|
|
55
|
+
def check_schema
|
|
56
|
+
if schema_installed?
|
|
57
|
+
say "ask_guest_sessions is in place.", :green
|
|
58
|
+
else
|
|
59
|
+
say "Run bin/rails db:migrate to create ask_guest_sessions.", :yellow
|
|
60
|
+
end
|
|
21
61
|
end
|
|
22
62
|
|
|
23
|
-
def
|
|
63
|
+
def show_next_steps
|
|
24
64
|
say <<~MESSAGE
|
|
25
65
|
|
|
26
66
|
ask-guests installed.
|
|
27
67
|
|
|
28
68
|
Next steps:
|
|
29
69
|
|
|
30
|
-
1.
|
|
70
|
+
1. Register what claiming transfers — one handler per record a
|
|
71
|
+
visitor owns (config/initializers/ask_guests.rb):
|
|
31
72
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# Destroying a session sweeps everything the guest owned:
|
|
36
|
-
# has_many :work_requests, foreign_key: :guest_session_id, dependent: :destroy
|
|
73
|
+
Ask::Guests.claimable(:chats) do |session, owner|
|
|
74
|
+
Chat.where(guest_session_id: session.id)
|
|
75
|
+
.update_all(user_id: owner.id, guest_session_id: nil)
|
|
37
76
|
end
|
|
38
77
|
|
|
39
|
-
2.
|
|
40
|
-
|
|
41
|
-
3. Allow guests in a controller:
|
|
78
|
+
2. Let guests in, in the controller that serves them:
|
|
42
79
|
|
|
43
80
|
include Ask::Guests::Rails::Controller
|
|
81
|
+
|
|
44
82
|
allow_guest_access
|
|
45
|
-
before_action :require_visitor
|
|
83
|
+
before_action :require_visitor, only: %i[chat create]
|
|
46
84
|
|
|
47
|
-
|
|
85
|
+
...and key their records to #current_guest_session.
|
|
86
|
+
|
|
87
|
+
3. Claim on authentication (Devise example):
|
|
48
88
|
|
|
49
89
|
include Ask::Guests::Rails::ClaimOnAuthentication
|
|
50
90
|
after_action :claim_guest_session, only: :create
|
|
51
91
|
|
|
52
|
-
def guest_claim_owner = current_user
|
|
92
|
+
def guest_claim_owner = current_user
|
|
53
93
|
|
|
54
|
-
|
|
94
|
+
4. Sweep abandoned sessions on a schedule (config/recurring.yml
|
|
95
|
+
with Solid Queue):
|
|
55
96
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
schedule: every day at 4am
|
|
97
|
+
sweep_guest_sessions:
|
|
98
|
+
command: "Ask::Guests::Rails::SweepJob.perform_now"
|
|
99
|
+
schedule: every day at 4am
|
|
60
100
|
|
|
61
101
|
MESSAGE
|
|
62
102
|
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
# A migration the host already has is the host's — it may have run
|
|
107
|
+
# already, so the gem does not touch it unless asked with --force.
|
|
108
|
+
def installed?(migration)
|
|
109
|
+
name = migration.sub(/\A\d+_/, "").delete_suffix(".rb")
|
|
110
|
+
self.class.migration_exists?(File.join(destination_root, "db/migrate"), name)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Advisory only: a database that is not there yet (db:create has not
|
|
114
|
+
# run, or the app boots without one) is not this generator's problem.
|
|
115
|
+
def schema_installed?
|
|
116
|
+
return false unless defined?(::ActiveRecord::Base)
|
|
117
|
+
|
|
118
|
+
::ActiveRecord::Base.connection.table_exists?("ask_guest_sessions")
|
|
119
|
+
rescue ::ActiveRecord::ActiveRecordError
|
|
120
|
+
false
|
|
121
|
+
end
|
|
63
122
|
end
|
|
64
123
|
end
|
|
65
124
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require "ask-guests/active_record"
|
|
2
|
+
|
|
3
|
+
# The ask_guest_sessions table, as the store reads and writes it.
|
|
4
|
+
#
|
|
5
|
+
# Nothing about a guest is a User row: a visitor is their own record, which is
|
|
6
|
+
# what lets an abandoned visit be swept and a visitor who signs up keep the
|
|
7
|
+
# threads they already had.
|
|
8
|
+
class GuestSession < Ask::Guests::ActiveRecord::SessionBase
|
|
9
|
+
self.table_name = "ask_guest_sessions"
|
|
10
|
+
|
|
11
|
+
# Everything a guest owns hangs off this row, so sweeping an abandoned
|
|
12
|
+
# session takes those records with it. Add one line per model that carries a
|
|
13
|
+
# guest_session_id — and make sure claiming moves them (see the initializer):
|
|
14
|
+
#
|
|
15
|
+
# has_many :chats, foreign_key: :guest_session_id, dependent: :destroy
|
|
16
|
+
end
|
|
@@ -1,42 +1,46 @@
|
|
|
1
|
-
|
|
1
|
+
require "ask-guests/active_record"
|
|
2
|
+
# The controller concern and the sweep job ride with Rails; the store is
|
|
3
|
+
# ActiveRecord's.
|
|
4
|
+
require "ask-guests/rails"
|
|
2
5
|
|
|
3
|
-
# Guest access
|
|
6
|
+
# Guest access (ask-guests).
|
|
4
7
|
#
|
|
5
|
-
# Anonymous visitors get a signed-cookie session, metered
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
+
# Anonymous visitors get a signed-cookie session, metered AI usage, and a
|
|
9
|
+
# single-use claim that moves everything they own to a real account when they
|
|
10
|
+
# sign up. See https://github.com/ask-rb/ask-guests for the full guide.
|
|
11
|
+
#
|
|
12
|
+
# Configured on prepare rather than at boot: the store holds the model class,
|
|
13
|
+
# and a reload must not leave it holding the class that was replaced.
|
|
14
|
+
Rails.application.config.to_prepare do
|
|
15
|
+
Ask::Guests.configure do |config|
|
|
16
|
+
# Required: HMAC secret for guest tokens. Rotating it invalidates all
|
|
17
|
+
# outstanding guest cookies (they simply start fresh).
|
|
18
|
+
config.secret = Rails.application.secret_key_base
|
|
8
19
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# outstanding guest cookies (they simply start fresh).
|
|
12
|
-
config.secret = Rails.application.secret_key_base
|
|
20
|
+
# Persistence: the model the install generator wrote over the migration.
|
|
21
|
+
config.store = Ask::Guests::ActiveRecord::SessionStore.new(model: GuestSession)
|
|
13
22
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
# Usage caps for anonymous visitors, one entry per category. Signing up
|
|
24
|
+
# lifts them because a signed-in request never touches the guest budget.
|
|
25
|
+
# Periods are :day, :week, or :month; counters roll over automatically.
|
|
26
|
+
# Empty means unlimited — appropriate when the host bills the visit some
|
|
27
|
+
# other way, and a cap needs a state to show the visitor when it bites.
|
|
28
|
+
config.categories = {
|
|
29
|
+
turns: {limit: 40, per: :day}
|
|
30
|
+
# tokens: {limit: 10_000, per: :day},
|
|
31
|
+
# uploads: {limit: 5, per: :day},
|
|
32
|
+
# messages: {limit: 100, per: :month}
|
|
33
|
+
}
|
|
21
34
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
config.categories = {
|
|
26
|
-
turns: {limit: 40, per: :day}
|
|
27
|
-
# tokens: {limit: 10_000, per: :day},
|
|
28
|
-
# uploads: {limit: 5, per: :day},
|
|
29
|
-
# messages: {limit: 100, per: :month}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
# Unclaimed sessions inactive for this long are swept (seconds).
|
|
33
|
-
config.retention = 14 * 24 * 60 * 60
|
|
35
|
+
# Unclaimed sessions inactive for this long are swept (seconds).
|
|
36
|
+
config.retention = 14 * 24 * 60 * 60
|
|
37
|
+
end
|
|
34
38
|
|
|
35
39
|
# What claiming transfers. Register one handler per owned model; they run
|
|
36
40
|
# inside the claim's transaction, exactly once.
|
|
37
41
|
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
+
# Ask::Guests.claimable(:work_requests) do |session, owner|
|
|
43
|
+
# WorkRequest.where(guest_session_id: session.id)
|
|
44
|
+
# .update_all(account_id: owner.account_id, account_user_id: owner.id, guest_session_id: nil)
|
|
45
|
+
# end
|
|
42
46
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ask-guests
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kaka Ruto
|
|
@@ -93,6 +93,20 @@ dependencies:
|
|
|
93
93
|
- - ">="
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '7.1'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: railties
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '7.1'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '7.1'
|
|
96
110
|
description: 'Give anonymous visitors a durable identity with metered AI access: signed
|
|
97
111
|
guest sessions, per-day turn/token budgets, single-use claiming of records into
|
|
98
112
|
a real owner on sign-up, and stale-session retention. Framework-agnostic core with
|
|
@@ -106,6 +120,7 @@ files:
|
|
|
106
120
|
- CHANGELOG.md
|
|
107
121
|
- LICENSE
|
|
108
122
|
- README.md
|
|
123
|
+
- db/migrate/20260917120000_create_ask_guest_sessions.rb
|
|
109
124
|
- lib/ask-guests.rb
|
|
110
125
|
- lib/ask-guests/active_record.rb
|
|
111
126
|
- lib/ask-guests/rails.rb
|
|
@@ -127,8 +142,8 @@ files:
|
|
|
127
142
|
- lib/ask/guests/token.rb
|
|
128
143
|
- lib/ask/guests/version.rb
|
|
129
144
|
- lib/generators/ask/guests/install/install_generator.rb
|
|
145
|
+
- lib/generators/ask/guests/install/templates/guest_session.rb
|
|
130
146
|
- lib/generators/ask/guests/install/templates/initializer.rb
|
|
131
|
-
- lib/generators/ask/guests/install/templates/migration.rb
|
|
132
147
|
homepage: https://github.com/ask-rb/ask-guests
|
|
133
148
|
licenses:
|
|
134
149
|
- MIT
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
class CreateGuestSessions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
-
def change
|
|
3
|
-
create_table :guest_sessions do |t|
|
|
4
|
-
t.jsonb :counters, null: false, default: {}
|
|
5
|
-
t.jsonb :metadata, null: false, default: {}
|
|
6
|
-
t.datetime :last_seen_at
|
|
7
|
-
t.datetime :converted_at
|
|
8
|
-
t.references :owner, polymorphic: true
|
|
9
|
-
t.timestamps
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# Speculative: sessions at risk of being swept.
|
|
13
|
-
add_index :guest_sessions, :converted_at
|
|
14
|
-
add_index :guest_sessions, :last_seen_at
|
|
15
|
-
end
|
|
16
|
-
end
|