database.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. const {defaultSettings} = require('./util/default.json');
  2. const {Client} = require('pg');
  3. const db = new Client();
  4. db.on( 'error', dberror => {
  5. console.log( '- Error while connecting to the database: ' + dberror );
  6. } );
  7. const schema = [`
  8. BEGIN TRANSACTION;
  9. CREATE TABLE patreons (
  10. patreon TEXT PRIMARY KEY
  11. UNIQUE
  12. NOT NULL,
  13. count INTEGER NOT NULL
  14. );
  15. CREATE INDEX idx_patreons_patreon ON patreons (
  16. patreon
  17. );
  18. CREATE TABLE discord (
  19. main TEXT UNIQUE
  20. CHECK (main = guild),
  21. guild TEXT NOT NULL
  22. REFERENCES discord (main) ON DELETE CASCADE,
  23. channel TEXT,
  24. wiki TEXT NOT NULL
  25. DEFAULT '${defaultSettings.wiki}',
  26. lang TEXT NOT NULL
  27. DEFAULT '${defaultSettings.lang}',
  28. role TEXT,
  29. inline INTEGER,
  30. prefix TEXT NOT NULL
  31. DEFAULT '${process.env.prefix}',
  32. patreon TEXT REFERENCES patreons (patreon) ON DELETE SET NULL,
  33. voice INTEGER,
  34. UNIQUE (
  35. guild,
  36. channel
  37. )
  38. );
  39. CREATE INDEX idx_discord_channel ON discord (
  40. guild,
  41. channel DESC
  42. NULLS LAST
  43. );
  44. CREATE INDEX idx_discord_patreon ON discord (
  45. patreon
  46. )
  47. WHERE patreon IS NOT NULL;
  48. CREATE INDEX idx_discord_voice ON discord (
  49. voice
  50. )
  51. WHERE voice IS NOT NULL;
  52. CREATE TABLE verification (
  53. guild TEXT NOT NULL
  54. REFERENCES discord (main) ON DELETE CASCADE,
  55. configid INTEGER NOT NULL,
  56. channel TEXT NOT NULL,
  57. role TEXT NOT NULL,
  58. editcount INTEGER NOT NULL
  59. DEFAULT 0,
  60. postcount INTEGER DEFAULT 0,
  61. usergroup TEXT NOT NULL
  62. DEFAULT 'user',
  63. accountage INTEGER NOT NULL
  64. DEFAULT 0,
  65. rename INTEGER NOT NULL
  66. DEFAULT 0,
  67. UNIQUE (
  68. guild,
  69. configid
  70. )
  71. );
  72. CREATE INDEX idx_verification_config ON verification (
  73. guild,
  74. configid ASC,
  75. channel
  76. );
  77. CREATE TABLE verifynotice (
  78. guild TEXT UNIQUE
  79. NOT NULL
  80. REFERENCES discord (main) ON DELETE CASCADE,
  81. logchannel TEXT,
  82. onsuccess TEXT,
  83. onmatch TEXT,
  84. flags INTEGER NOT NULL
  85. DEFAULT 0
  86. );
  87. CREATE INDEX idx_verifynotice_guild ON verifynotice (
  88. guild
  89. );
  90. CREATE TABLE oauthusers (
  91. userid TEXT NOT NULL,
  92. site TEXT NOT NULL,
  93. token TEXT,
  94. UNIQUE (
  95. userid,
  96. site
  97. )
  98. );
  99. CREATE INDEX idx_oauthusers_userid ON oauthusers (
  100. userid,
  101. site
  102. );
  103. CREATE TABLE rcgcdw (
  104. guild TEXT NOT NULL
  105. REFERENCES discord (main) ON DELETE CASCADE,
  106. configid INTEGER NOT NULL,
  107. webhook TEXT NOT NULL
  108. UNIQUE,
  109. wiki TEXT NOT NULL,
  110. lang TEXT NOT NULL
  111. DEFAULT '${defaultSettings.lang}',
  112. display INTEGER NOT NULL
  113. DEFAULT 1,
  114. rcid INTEGER,
  115. postid TEXT DEFAULT '-1',
  116. UNIQUE (
  117. guild,
  118. configid
  119. )
  120. );
  121. CREATE INDEX idx_rcgcdw_wiki ON rcgcdw (
  122. wiki
  123. );
  124. CREATE INDEX idx_rcgcdw_webhook ON rcgcdw (
  125. webhook
  126. );
  127. CREATE INDEX idx_rcgcdw_config ON rcgcdw (
  128. guild,
  129. configid ASC
  130. );
  131. CREATE TABLE blocklist (
  132. wiki TEXT UNIQUE
  133. NOT NULL,
  134. reason TEXT
  135. );
  136. CREATE INDEX idx_blocklist_wiki ON blocklist (
  137. wiki
  138. );
  139. COMMIT TRANSACTION;
  140. ALTER DATABASE "${process.env.PGDATABASE}" SET my.version TO 4;
  141. `,`
  142. BEGIN TRANSACTION;
  143. CREATE TABLE verifynotice (
  144. guild TEXT UNIQUE
  145. NOT NULL
  146. REFERENCES discord (main) ON DELETE CASCADE,
  147. logchannel TEXT,
  148. onsuccess TEXT,
  149. onmatch TEXT
  150. );
  151. CREATE INDEX idx_verifynotice_guild ON verifynotice (
  152. guild
  153. );
  154. COMMIT TRANSACTION;
  155. ALTER DATABASE "${process.env.PGDATABASE}" SET my.version TO 2;
  156. `,`
  157. BEGIN TRANSACTION;
  158. ALTER TABLE verifynotice
  159. ADD COLUMN flags INTEGER NOT NULL DEFAULT 0;
  160. COMMIT TRANSACTION;
  161. ALTER DATABASE "${process.env.PGDATABASE}" SET my.version TO 3;
  162. `,`
  163. BEGIN TRANSACTION;
  164. CREATE TABLE oauthusers (
  165. userid TEXT NOT NULL,
  166. site TEXT NOT NULL,
  167. token TEXT,
  168. UNIQUE (
  169. userid,
  170. site
  171. )
  172. );
  173. CREATE INDEX idx_oauthusers_userid ON oauthusers (
  174. userid,
  175. site
  176. );
  177. COMMIT TRANSACTION;
  178. ALTER DATABASE "${process.env.PGDATABASE}" SET my.version TO 4;
  179. `];
  180. module.exports = db.connect().then( () => {
  181. return db.query( 'SELECT CURRENT_SETTING($1) AS version', ['my.version'] ).then( ({rows:[row]}) => {
  182. row.version = parseInt(row.version, 10);
  183. if ( isNaN(row.version) || row.version > schema.length ) {
  184. console.log( '- Invalid database version: v' + row.version );
  185. return Promise.reject();
  186. }
  187. if ( row.version === schema.length ) {
  188. console.log( '- The database is up to date: v' + row.version );
  189. return;
  190. }
  191. console.log( '- The database is outdated: v' + row.version );
  192. if ( process.env.READONLY ) return Promise.reject();
  193. return db.query( schema.filter( (sql, version) => {
  194. if ( row.version === 0 ) return ( version === 0 );
  195. return ( row.version <= version );
  196. } ).join('\n') ).then( () => {
  197. console.log( '- The database has been updated to: v' + schema.length );
  198. }, dberror => {
  199. console.log( '- Error while updating the database: ' + dberror );
  200. return Promise.reject();
  201. } );
  202. }, dberror => {
  203. if ( dberror.message === 'unrecognized configuration parameter "my.version"' ) {
  204. return db.query( schema[0] ).then( () => {
  205. console.log( '- The database has been updated to: v' + schema.length );
  206. }, dberror => {
  207. console.log( '- Error while updating the database: ' + dberror );
  208. return Promise.reject();
  209. } );
  210. }
  211. console.log( '- Error while getting the database version: ' + dberror );
  212. return Promise.reject();
  213. } );
  214. }, dberror => {
  215. console.log( '- Error while connecting to the database: ' + dberror );
  216. return Promise.reject();
  217. } ).then( () => {
  218. db.end().catch( dberror => {
  219. console.log( '- Error while closing the database connection: ' + dberror );
  220. } );
  221. }, () => {
  222. return db.end().then( () => {
  223. console.log( '- Closed the database connection.' );
  224. }, dberror => {
  225. console.log( '- Error while closing the database connection: ' + dberror );
  226. } ).then( () => {
  227. return Promise.reject();
  228. } );
  229. } );