chat_sdk-state-pg 0.2.1
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 +7 -0
- data/lib/chat_sdk/state/pg.rb +176 -0
- metadata +71 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: b581b9863bfefec84597d4c07cea4379928f1385aec9a3ea87142dfe41df8e1e
|
|
4
|
+
data.tar.gz: 5aa8b46ba8515bc20acaba7d4985534f040e839f04a6d40b12dc6348df26927f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 524b58cf98323b21b565e33627f84ace837f93a1fc640353069e82b21110270d98f35ca1ee7c35176df910af67110282c49aad4e33801a24be1a1f5f871ad99c
|
|
7
|
+
data.tar.gz: f3db506a1c1ed0cdb1b2882ee1f80f7af18dd3001b4ec8c4e7e777cbb31d326a7f5672473bd35f8d621b41a3f557396ab123670beb7b7a60e3469df7825d454f
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "chat_sdk"
|
|
4
|
+
require "pg"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module ChatSDK
|
|
8
|
+
module State
|
|
9
|
+
class Pg < Base
|
|
10
|
+
def initialize(url: nil, connection: nil, key_prefix: "chat_sdk", auto_migrate: true)
|
|
11
|
+
@conn = connection || PG.connect(url || ENV["DATABASE_URL"] || ENV["POSTGRES_URL"] || "postgresql://localhost/chat_sdk_dev")
|
|
12
|
+
@key_prefix = key_prefix
|
|
13
|
+
ensure_tables if auto_migrate
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :conn
|
|
17
|
+
|
|
18
|
+
# Subscriptions
|
|
19
|
+
|
|
20
|
+
def subscribe(thread_id)
|
|
21
|
+
@conn.exec_params(
|
|
22
|
+
"INSERT INTO chat_sdk_subscriptions (key_prefix, thread_id) VALUES ($1, $2) ON CONFLICT DO NOTHING",
|
|
23
|
+
[@key_prefix, thread_id]
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def unsubscribe(thread_id)
|
|
28
|
+
@conn.exec_params(
|
|
29
|
+
"DELETE FROM chat_sdk_subscriptions WHERE key_prefix = $1 AND thread_id = $2",
|
|
30
|
+
[@key_prefix, thread_id]
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def subscribed?(thread_id)
|
|
35
|
+
result = @conn.exec_params(
|
|
36
|
+
"SELECT 1 FROM chat_sdk_subscriptions WHERE key_prefix = $1 AND thread_id = $2",
|
|
37
|
+
[@key_prefix, thread_id]
|
|
38
|
+
)
|
|
39
|
+
result.ntuples > 0
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Locks
|
|
43
|
+
|
|
44
|
+
def acquire_lock(key, owner:, ttl:)
|
|
45
|
+
ttl_seconds = ttl.to_f
|
|
46
|
+
@conn.exec_params(
|
|
47
|
+
"DELETE FROM chat_sdk_locks WHERE key_prefix = $1 AND lock_key = $2 AND expires_at < NOW()",
|
|
48
|
+
[@key_prefix, key]
|
|
49
|
+
)
|
|
50
|
+
result = @conn.exec_params(
|
|
51
|
+
"INSERT INTO chat_sdk_locks (key_prefix, lock_key, owner, expires_at) " \
|
|
52
|
+
"VALUES ($1, $2, $3, NOW() + INTERVAL '#{ttl_seconds} seconds') ON CONFLICT DO NOTHING",
|
|
53
|
+
[@key_prefix, key, owner]
|
|
54
|
+
)
|
|
55
|
+
result.cmd_tuples > 0
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def release_lock(key, owner:)
|
|
59
|
+
result = @conn.exec_params(
|
|
60
|
+
"DELETE FROM chat_sdk_locks WHERE key_prefix = $1 AND lock_key = $2 AND owner = $3",
|
|
61
|
+
[@key_prefix, key, owner]
|
|
62
|
+
)
|
|
63
|
+
result.cmd_tuples > 0
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def force_lock(key, owner:, ttl:)
|
|
67
|
+
ttl_seconds = ttl.to_f
|
|
68
|
+
@conn.exec_params(
|
|
69
|
+
"INSERT INTO chat_sdk_locks (key_prefix, lock_key, owner, expires_at) " \
|
|
70
|
+
"VALUES ($1, $2, $3, NOW() + INTERVAL '#{ttl_seconds} seconds') " \
|
|
71
|
+
"ON CONFLICT (key_prefix, lock_key) DO UPDATE SET owner = EXCLUDED.owner, expires_at = EXCLUDED.expires_at",
|
|
72
|
+
[@key_prefix, key, owner]
|
|
73
|
+
)
|
|
74
|
+
true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Key-value store
|
|
78
|
+
|
|
79
|
+
def get(key)
|
|
80
|
+
result = @conn.exec_params(
|
|
81
|
+
"SELECT value FROM chat_sdk_cache WHERE key_prefix = $1 AND cache_key = $2 AND (expires_at IS NULL OR expires_at > NOW())",
|
|
82
|
+
[@key_prefix, key]
|
|
83
|
+
)
|
|
84
|
+
return JSON.parse(result[0]["value"]) if result.ntuples > 0
|
|
85
|
+
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def set(key, value, ttl: nil)
|
|
90
|
+
serialized = JSON.generate(value)
|
|
91
|
+
expires = ttl ? "NOW() + INTERVAL '#{ttl.to_f} seconds'" : "NULL"
|
|
92
|
+
@conn.exec_params(
|
|
93
|
+
"INSERT INTO chat_sdk_cache (key_prefix, cache_key, value, expires_at) " \
|
|
94
|
+
"VALUES ($1, $2, $3::jsonb, #{expires}) " \
|
|
95
|
+
"ON CONFLICT (key_prefix, cache_key) DO UPDATE SET value = EXCLUDED.value, expires_at = EXCLUDED.expires_at",
|
|
96
|
+
[@key_prefix, key, serialized]
|
|
97
|
+
)
|
|
98
|
+
value
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def delete(key)
|
|
102
|
+
@conn.exec_params(
|
|
103
|
+
"DELETE FROM chat_sdk_cache WHERE key_prefix = $1 AND cache_key = $2",
|
|
104
|
+
[@key_prefix, key]
|
|
105
|
+
)
|
|
106
|
+
@conn.exec_params(
|
|
107
|
+
"DELETE FROM chat_sdk_locks WHERE key_prefix = $1 AND lock_key = $2",
|
|
108
|
+
[@key_prefix, key]
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def set_if_absent(key, value, ttl: nil)
|
|
113
|
+
serialized = JSON.generate(value)
|
|
114
|
+
@conn.exec_params(
|
|
115
|
+
"DELETE FROM chat_sdk_cache WHERE key_prefix = $1 AND cache_key = $2 AND expires_at IS NOT NULL AND expires_at < NOW()",
|
|
116
|
+
[@key_prefix, key]
|
|
117
|
+
)
|
|
118
|
+
expires = ttl ? "NOW() + INTERVAL '#{ttl.to_f} seconds'" : "NULL"
|
|
119
|
+
result = @conn.exec_params(
|
|
120
|
+
"INSERT INTO chat_sdk_cache (key_prefix, cache_key, value, expires_at) " \
|
|
121
|
+
"VALUES ($1, $2, $3::jsonb, #{expires}) ON CONFLICT DO NOTHING",
|
|
122
|
+
[@key_prefix, key, serialized]
|
|
123
|
+
)
|
|
124
|
+
result.cmd_tuples > 0
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Cleanup
|
|
128
|
+
|
|
129
|
+
def clear
|
|
130
|
+
@conn.exec_params("DELETE FROM chat_sdk_subscriptions WHERE key_prefix = $1", [@key_prefix])
|
|
131
|
+
@conn.exec_params("DELETE FROM chat_sdk_locks WHERE key_prefix = $1", [@key_prefix])
|
|
132
|
+
@conn.exec_params("DELETE FROM chat_sdk_cache WHERE key_prefix = $1", [@key_prefix])
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def cleanup_expired
|
|
136
|
+
@conn.exec_params(
|
|
137
|
+
"DELETE FROM chat_sdk_cache WHERE key_prefix = $1 AND expires_at IS NOT NULL AND expires_at < NOW()",
|
|
138
|
+
[@key_prefix]
|
|
139
|
+
)
|
|
140
|
+
@conn.exec_params(
|
|
141
|
+
"DELETE FROM chat_sdk_locks WHERE key_prefix = $1 AND expires_at < NOW()",
|
|
142
|
+
[@key_prefix]
|
|
143
|
+
)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
148
|
+
def ensure_tables
|
|
149
|
+
@conn.exec(<<~SQL)
|
|
150
|
+
CREATE TABLE IF NOT EXISTS chat_sdk_subscriptions (
|
|
151
|
+
key_prefix VARCHAR(255) NOT NULL,
|
|
152
|
+
thread_id VARCHAR(512) NOT NULL,
|
|
153
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
154
|
+
PRIMARY KEY (key_prefix, thread_id)
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
CREATE TABLE IF NOT EXISTS chat_sdk_locks (
|
|
158
|
+
key_prefix VARCHAR(255) NOT NULL,
|
|
159
|
+
lock_key VARCHAR(512) NOT NULL,
|
|
160
|
+
owner VARCHAR(255) NOT NULL,
|
|
161
|
+
expires_at TIMESTAMP NOT NULL,
|
|
162
|
+
PRIMARY KEY (key_prefix, lock_key)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
CREATE TABLE IF NOT EXISTS chat_sdk_cache (
|
|
166
|
+
key_prefix VARCHAR(255) NOT NULL,
|
|
167
|
+
cache_key VARCHAR(512) NOT NULL,
|
|
168
|
+
value JSONB NOT NULL,
|
|
169
|
+
expires_at TIMESTAMP,
|
|
170
|
+
PRIMARY KEY (key_prefix, cache_key)
|
|
171
|
+
);
|
|
172
|
+
SQL
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: chat_sdk-state-pg
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Quentin Rousseau
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: chat_sdk
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: pg
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.5'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.5'
|
|
40
|
+
description: PostgreSQL-backed conversation state storage for the ChatSDK framework
|
|
41
|
+
with auto-migration and JSONB storage
|
|
42
|
+
email:
|
|
43
|
+
- quentin@rootly.com
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- lib/chat_sdk/state/pg.rb
|
|
49
|
+
homepage: https://github.com/rootlyhq/chat-sdk
|
|
50
|
+
licenses:
|
|
51
|
+
- MIT
|
|
52
|
+
metadata:
|
|
53
|
+
rubygems_mfa_required: 'true'
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 3.2.0
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 4.0.16
|
|
69
|
+
specification_version: 4
|
|
70
|
+
summary: PostgreSQL state backend for ChatSDK
|
|
71
|
+
test_files: []
|