@@ -24,13 +24,25 @@ class App < Roda
2424 opts [ :dataset_items ] = items
2525 end
2626
27- PG_QUERY = 'SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3' . freeze
27+ CRUD_COLUMNS = 'id, name, category, price, quantity, active, tags, rating_score, rating_count'
28+ SELECT_QUERY = "SELECT #{ CRUD_COLUMNS } FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3" . freeze
29+ CRUD_GET_SQL = "SELECT #{ CRUD_COLUMNS } FROM items WHERE id = $1 LIMIT 1"
30+ CRUD_LIST_SQL = "SELECT #{ CRUD_COLUMNS } FROM items WHERE category = $1 ORDER BY id LIMIT $2 OFFSET $3"
31+ CRUD_UPDATE_SQL = "UPDATE items SET name = $1, price = $2, quantity = $3 WHERE id = $4"
32+ CRUD_UPSERT_SQL = <<~SQL
33+ INSERT INTO items
34+ (#{ CRUD_COLUMNS } )
35+ VALUES ($1, $2, $3, $4, $5, true, '[\" bench\" ]', 0, 0)
36+ ON CONFLICT (id) DO UPDATE SET name = $2, price = $4, quantity = $5
37+ RETURNING id
38+ SQL
2839
2940 plugin :public , root : DATA_DIR , gzip : true , brotli : true
3041 plugin :request_headers
3142 plugin :plain_hash_response_headers
3243 plugin :halt
3344 plugin :send_file
45+ plugin :all_verbs
3446
3547 route do |r |
3648 r . root { 'ok' }
@@ -83,20 +95,109 @@ class App < Roda
8395 end || [ ]
8496
8597 items = rows . map do |row |
86- {
87- id : row [ 'id' ] ,
88- name : row [ 'name' ] ,
89- category : row [ 'category' ] ,
90- price : row [ 'price' ] ,
91- quantity : row [ 'quantity' ] ,
92- active : row [ 'active' ] == 1 ,
93- tags : JSON . parse ( row [ 'tags' ] ) ,
94- rating : { score : row [ 'rating_score' ] , count : row [ 'rating_count' ] }
95- }
98+ map_row ( row )
9699 end
97100 render_json JSON . generate ( { items : items , count : items . length } )
98101 end
99102
103+ r . is 'crud/items' do
104+ r . get do
105+ category = request . params [ 'category' ] || 'electronics'
106+ page = ( request . params [ 'page' ] || 1 ) . to_i
107+ limit = ( request . params [ 'limit' ] || 10 ) . to_i
108+ offset = ( page - 1 ) * limit
109+
110+ rows = self . class . get_async_db &.with do |connection |
111+ connection . exec_prepared ( 'crud_list' , [ category , limit , offset ] )
112+ end || [ ]
113+
114+ items = rows . map do |row |
115+ map_row ( row )
116+ end
117+ render_json JSON . generate ( { items : items , total : items . length , page : page , limit : limit } )
118+ end
119+
120+ r . post do
121+ params = JSON . parse ( request . body . read )
122+ id = params [ 'id' ]
123+ name = params [ 'name' ] || 'New Product'
124+ category = params [ 'category' ] || 'electronics'
125+ price = ( params [ 'price' ] || 0 ) . to_i
126+ quantity = ( params [ 'quantity' ] || 0 ) . to_i
127+
128+ self . class . get_async_db &.with do |connection |
129+ connection . exec_prepared ( 'crud_upsert' , [ id , name , category , price , quantity ] )
130+ end
131+
132+ self . class . redis &.with do |connection |
133+ connection . del ( id . to_s )
134+ end
135+
136+ item = {
137+ 'id' => id ,
138+ 'name' => name ,
139+ 'category' => category ,
140+ 'price' => price ,
141+ 'quantity' => quantity
142+ }
143+
144+ response . status = 201
145+ render_json JSON . generate ( item )
146+ end
147+ end
148+
149+ r . is 'crud/items' , Integer do |id |
150+ r . get do
151+ json = self . class . redis &.with do |connection |
152+ connection . get ( id . to_s )
153+ end
154+ if json
155+ response [ 'x-cache' ] = 'HIT'
156+ return render_json json
157+ else
158+ response [ 'x-cache' ] = 'MISS'
159+ end
160+
161+ rows = self . class . get_async_db &.with do |connection |
162+ connection . exec_prepared ( 'crud_get' , [ id ] )
163+ end || [ ]
164+
165+ if row = rows . first
166+ item = map_row ( row )
167+ json = JSON . generate ( item )
168+ self . class . redis &.with do |connection |
169+ connection . set ( id . to_s , json )
170+ end
171+ render_json json
172+ else
173+ r . halt 404 , 'Not found'
174+ end
175+ end
176+
177+ r . put do
178+ params = JSON . parse ( request . body . read )
179+ name = params [ 'name' ] || 'New Product'
180+ price = ( params [ 'price' ] || 0 ) . to_i
181+ quantity = ( params [ 'quantity' ] || 0 ) . to_i
182+
183+ row = self . class . get_async_db &.with do |connection |
184+ connection . exec_prepared ( 'crud_update' , [ name , price , quantity , id ] )
185+ end || [ ]
186+
187+ self . class . redis &.with do |connection |
188+ connection . del ( id . to_s )
189+ end
190+
191+ item = {
192+ 'id' => id ,
193+ 'name' => name ,
194+ 'price' => price ,
195+ 'quantity' => quantity
196+ }
197+ render_json JSON . generate ( item )
198+ end
199+ end
200+
100201 r . public
101202 end
102203
@@ -112,15 +213,44 @@ def render_plain(plain)
112213 plain
113214 end
114215
216+ def map_row ( row )
217+ mapped_row = {
218+ id : row [ 'id' ] ,
219+ name : row [ 'name' ] ,
220+ category : row [ 'category' ] ,
221+ price : row [ 'price' ] ,
222+ quantity : row [ 'quantity' ] ,
223+ active : row [ 'active' ] == 1 ,
224+ }
225+ mapped_row [ :tags ] = JSON . parse ( row [ 'tags' ] ) if row [ 'tags' ]
226+ mapped_row [ :rating ] = { score : row [ 'rating_score' ] , count : row [ 'rating_count' ] } if row [ 'rating_score' ] && row [ 'rating_count' ]
227+ mapped_row
228+ end
229+
115230 def self . get_async_db
116231 @async_db ||= begin
117232 return unless ENV [ 'DATABASE_URL' ]
118233 max_connections = ENV . fetch ( 'MAX_THREADS' , 4 ) . to_i
119234 ConnectionPool . new ( size : max_connections , timeout : 5 ) do
120235 db = PG . connect ( ENV [ 'DATABASE_URL' ] )
121- db . prepare ( 'select' , PG_QUERY )
236+ db . prepare ( 'select' , SELECT_QUERY )
237+ db . prepare ( 'crud_get' , CRUD_GET_SQL )
238+ db . prepare ( 'crud_list' , CRUD_LIST_SQL )
239+ db . prepare ( 'crud_update' , CRUD_UPDATE_SQL )
240+ db . prepare ( 'crud_upsert' , CRUD_UPSERT_SQL )
122241 db
123242 end
124243 end
125244 end
245+
246+ def self . redis
247+ @redis ||= begin
248+ return unless ENV [ 'REDIS_URL' ]
249+ max_connections = ENV . fetch ( 'MAX_THREADS' , 4 ) . to_i
250+ ConnectionPool ::Wrapper . new ( size : max_connections , timeout : 10 ) do
251+ Redis . new ( url : ENV [ 'REDIS_URL' ] )
252+ end
253+ end
254+ end
255+
126256end
0 commit comments