db.lisp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. (in-package #:chatikbot)
  2. (defvar *db-name* "db.sqlite" "SQLite database name")
  3. (defun db-path ()
  4. (merge-pathnames *db-name*
  5. (asdf:component-pathname
  6. (asdf:find-system '#:chatikbot))))
  7. (defmacro with-db ((db) &body body)
  8. `(sqlite:with-open-database (,db (db-path) :busy-timeout 30)
  9. (sqlite:execute-non-query ,db "PRAGMA foreign_keys = ON")
  10. ,@body))
  11. (defun db-execute (sql &rest parameters)
  12. (with-db (db)
  13. (apply #'sqlite:execute-non-query db sql parameters)))
  14. (defun db-select (sql &rest parameters)
  15. (with-db (db)
  16. (apply #'sqlite:execute-to-list db sql parameters)))
  17. (defmacro def-db-init (() &body body)
  18. `(add-hook :db-init #'(lambda ()
  19. (handler-case (progn ,@body)
  20. (error (e) (log:error e)))
  21. (values))))
  22. (defun db-init ()
  23. (with-db (db)
  24. (run-hooks :db-init)
  25. ;; Finance
  26. (sqlite:execute-non-query db "create table if not exists finance (ts, usd, eur, gbp, brent, btc)")
  27. (sqlite:execute-non-query db "create index if not exists fin_ts_ids on finance (ts)")
  28. ;; Foursquare
  29. (sqlite:execute-non-query db "create table if not exists fsq_chat_users (chat_id, user_id)")
  30. (sqlite:execute-non-query db "create index if not exists fsq_chat_users_chat_idx on fsq_chat_users (chat_id)")
  31. (sqlite:execute-non-query db "create index if not exists fsq_chat_users_user_idx on fsq_chat_users (user_id)")
  32. (sqlite:execute-non-query db "create table if not exists fsq_seen (id, created_at)")
  33. (sqlite:execute-non-query db "create index if not exists fsq_seen_idx on fsq_seen (id)")
  34. ;; RSS
  35. (sqlite:execute-non-query db "create table if not exists rss_feeds (id INTEGER PRIMARY KEY, url, title, next_fetch, period)")
  36. (sqlite:execute-non-query db "create unique index if not exists rss_feeds_url_idx on rss_feeds (url)")
  37. (sqlite:execute-non-query db "create table if not exists rss_items (id INTEGER PRIMARY KEY, feed_id REFERENCES rss_feeds, guid, link, title, published)")
  38. (sqlite:execute-non-query db "create index if not exists rss_items_idx on rss_items (feed_id, guid)")
  39. (sqlite:execute-non-query db "create table if not exists rss_chat_feeds (chat_id, feed_id REFERENCES rss_feeds)")
  40. (sqlite:execute-non-query db "create index if not exists rss_chat_feeds_chat_idx on rss_chat_feeds (chat_id)")
  41. (sqlite:execute-non-query db "create index if not exists rss_chat_feeds_feed_idx on rss_chat_feeds (feed_id)")
  42. ;; VK
  43. (sqlite:execute-non-query db "create table if not exists vk_walls (domain, last_id, next_fetch, period)")
  44. (sqlite:execute-non-query db "create unique index if not exists vk_walls_domain_idx on vk_walls (domain)")
  45. (sqlite:execute-non-query db "create table if not exists vk_chat_walls (chat_id, domain)")
  46. (sqlite:execute-non-query db "create index if not exists vk_chat_walls_chat_idx on vk_chat_walls (chat_id)")
  47. (sqlite:execute-non-query db "create index if not exists vk_chat_walls_domain_idx on vk_chat_walls (domain)")))
  48. ;; Finance
  49. (defun db-add-finance (ts usd eur gbp brent btc)
  50. (with-db (db)
  51. (sqlite:execute-non-query db "insert into finance (ts, usd, eur, gbp, brent, btc) values (?, ?, ?, ?, ?, ?)"
  52. ts usd eur gbp brent btc)))
  53. (defun db-get-last-finance ()
  54. (with-db (db)
  55. (sqlite:execute-one-row-m-v db "select ts, usd, eur, gbp, brent, btc from finance order by ts desc limit 1")))
  56. (defparameter +db-finance-map+ '(("usd" . "USD/RUB")
  57. ("eur" . "EUR/RUB")
  58. ("gbp" . "GBP/RUB")
  59. ("brent" . "Brent")
  60. ("btc" . "BTC/USD")))
  61. (defun db-get-series (after-ts &optional (fields '("usd" "eur" "gbp" "brent")) (avg 60))
  62. (when fields
  63. (let ((sql (format nil
  64. "select ts/~a*~:*~a,~{avg(~a) as ~:*~a~^,~} from finance where ts >= ? group by ts/~a order by ts"
  65. avg fields avg)))
  66. (with-db (db)
  67. (values
  68. (sqlite:execute-to-list db sql (local-time:timestamp-to-unix after-ts))
  69. (sublis +db-finance-map+ fields :test #'equal))))))
  70. ;; Foursquare
  71. (defun db-fsq-get-chat-users (chat-id)
  72. (flatten (with-db (db)
  73. (sqlite:execute-to-list db "select user_id from fsq_chat_users where chat_id = ?" chat-id))))
  74. (defun db-fsq-get-user-chats (user-id)
  75. (flatten (with-db (db)
  76. (sqlite:execute-to-list db "select chat_id from fsq_chat_users where user_id = ?" user-id))))
  77. (defun db-fsq-set-chat-users (chat-id users)
  78. (with-db (db)
  79. (sqlite:with-transaction db
  80. (sqlite:execute-non-query db "delete from fsq_chat_users where chat_id = ?" chat-id)
  81. (dolist (user-id users)
  82. (sqlite:execute-non-query db "insert into fsq_chat_users (chat_id, user_id) values (?, ?)" chat-id user-id)))))
  83. (defun db-fsq-add-seen (id created-at)
  84. (with-db (db)
  85. (sqlite:execute-non-query db "insert into fsq_seen (id, created_at) values (?, ?)" id created-at)))
  86. (defun db-fsq-has-seen (id)
  87. (with-db (db)
  88. (sqlite:execute-single db "select id from fsq_seen where id = ? limit 1" id)))
  89. (defun db-fsq-last-created ()
  90. (with-db (db)
  91. (sqlite:execute-single db "select created_at from fsq_seen order by created_at desc limit 1")))
  92. ;; RSS
  93. (defun %make-feed (row)
  94. (when row
  95. (make-feed :id (nth 0 row)
  96. :url (nth 1 row)
  97. :title (nth 2 row)
  98. :next-fetch (when (nth 3 row) (local-time:unix-to-timestamp (nth 3 row)))
  99. :period (nth 4 row))))
  100. (defun db-rss-get-feed-by-url (url)
  101. (with-db (db)
  102. (%make-feed (car (sqlite:execute-to-list db "select id, url, title, next_fetch, period from rss_feeds where url = ?" url)))))
  103. (defun db-rss-add-feed (feed)
  104. (with-slots (url title period) feed
  105. (let ((next-fetch (feed-next-fetch-unix feed)))
  106. (with-db (db)
  107. (sqlite:execute-non-query db "insert into rss_feeds (url, title, next_fetch, period) values (?, ?, ?, ?)"
  108. url title next-fetch period)
  109. (setf (feed-id feed) (sqlite:last-insert-rowid db))
  110. feed))))
  111. (defun db-rss-update-feed (feed)
  112. (with-slots (id title period) feed
  113. (let ((next-fetch (feed-next-fetch-unix feed)))
  114. (with-db (db)
  115. (sqlite:execute-non-query db "update rss_feeds set title = ?, next_fetch = ?, period = ? where id = ?"
  116. title next-fetch period id)))))
  117. (defun db-rss-get-active-feeds ()
  118. (with-db (db)
  119. (mapcar #'%make-feed
  120. (sqlite:execute-to-list db "select id, url, title, next_fetch, period from rss_feeds where exists (select 1 from rss_chat_feeds where feed_id=id)"))))
  121. (defun db-rss-get-chat-feeds (chat-id)
  122. (with-db (db)
  123. (mapcar #'%make-feed
  124. (sqlite:execute-to-list db "select id, url, title, next_fetch, period from rss_feeds where id in (select feed_id from rss_chat_feeds where chat_id = ?)" chat-id))))
  125. (defun db-rss-get-feed-chats (feed)
  126. (with-slots (id) feed
  127. (flatten (with-db (db)
  128. (sqlite:execute-to-list db "select chat_id from rss_chat_feeds where feed_id = ?" id)))))
  129. (defun db-rss-set-chat-feeds (chat-id feeds)
  130. (with-db (db)
  131. (sqlite:with-transaction db
  132. (sqlite:execute-non-query db "delete from rss_chat_feeds where chat_id = ?" chat-id)
  133. (dolist (feed feeds)
  134. (with-slots (id) feed
  135. (sqlite:execute-non-query db "insert into rss_chat_feeds (chat_id, feed_id) values (?, ?)" chat-id id))))))
  136. (defun db-rss-item-exists (item)
  137. (let ((feed-id (feed-id (feed-item-feed item)))
  138. (guid (feed-item-guid item)))
  139. (with-db (db)
  140. (sqlite:execute-single db "select id from rss_items where feed_id = ? and guid = ? limit 1"
  141. feed-id guid))))
  142. (defun db-rss-add-item (item)
  143. (let ((feed-id (feed-id (feed-item-feed item)))
  144. (published (feed-item-published-unix item)))
  145. (with-slots (guid link title) item
  146. (with-db (db)
  147. (sqlite:execute-non-query db "insert into rss_items (feed_id, guid, link, title, published) values (?, ?, ?, ?, ?)"
  148. feed-id guid link title published)))))
  149. (defun %make-feed-item (feed row)
  150. (when row
  151. (make-feed-item :feed feed
  152. :guid (nth 0 row)
  153. :link (nth 1 row)
  154. :title (nth 2 row)
  155. :published (when (nth 3 row) (local-time:unix-to-timestamp (nth 3 row))))))
  156. (defun db-rss-last-feed-items (feed &optional (limit 10))
  157. (with-slots (id) feed
  158. (with-db (db)
  159. (mapcar #'(lambda (row) (%make-feed-item feed row))
  160. (sqlite:execute-to-list db "select guid, link, title, published from rss_items where feed_id = ? order by published desc, id desc limit ?" id limit)))))
  161. ;; VK
  162. (defun db-vk-ensure-domain (domain last-id)
  163. (with-db (db)
  164. (unless (sqlite:execute-single db "select domain from vk_walls where domain = ?" domain)
  165. (sqlite:execute-non-query db "insert into vk_walls (domain, last_id, period) values (?, ?, 300)" domain last-id))))
  166. (defun db-vk-get-domain-chats (domain)
  167. (with-db (db)
  168. (flatten (sqlite:execute-to-list db "select chat_id from vk_chat_walls where domain = ?" domain))))
  169. (defun db-vk-get-chat-domains (chat-id)
  170. (with-db (db)
  171. (flatten (sqlite:execute-to-list db "select domain from vk_chat_walls where chat_id = ?" chat-id))))
  172. (defun db-vk-add-chat-domain (chat-id domain)
  173. (with-db (db)
  174. (sqlite:execute-non-query db "insert into vk_chat_walls (chat_id, domain) values (?, ?)" chat-id domain)))
  175. (defun db-vk-remove-chat-domain (chat-id domain)
  176. (with-db (db)
  177. (sqlite:execute-non-query db "delete from vk_chat_walls where chat_id = ? and domain = ?" chat-id domain)))
  178. (defun db-vk-get-active-walls ()
  179. (with-db (db)
  180. (sqlite:execute-to-list db "select domain, last_id, next_fetch, period from vk_walls w where exists (select 1 from vk_chat_walls where domain=w.domain)")))
  181. (defun db-vk-update-wall (domain last-id next-fetch period)
  182. (with-db (db)
  183. (sqlite:execute-to-list db "update vk_walls set last_id = ?, next_fetch = ?, period = ? where domain = ?" last-id next-fetch period domain)))