database.js 6.3 KB

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