database.js 6.6 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 IF NOT EXISTS patreons (
  12. patreon TEXT PRIMARY KEY
  13. UNIQUE
  14. NOT NULL,
  15. count INTEGER NOT NULL
  16. );
  17. CREATE INDEX IF NOT EXISTS idx_patreons_patreon ON patreons (
  18. patreon
  19. );
  20. CREATE TABLE IF NOT EXISTS 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 IF NOT EXISTS idx_discord_channel ON discord (
  42. guild,
  43. channel DESC
  44. NULLS LAST
  45. );
  46. CREATE INDEX IF NOT EXISTS idx_discord_patreon ON discord (
  47. patreon
  48. )
  49. WHERE patreon IS NOT NULL;
  50. CREATE INDEX IF NOT EXISTS idx_discord_voice ON discord (
  51. voice
  52. )
  53. WHERE voice IS NOT NULL;
  54. CREATE TABLE IF NOT EXISTS 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 IF NOT EXISTS idx_verification_config ON verification (
  75. guild,
  76. configid ASC,
  77. channel
  78. );
  79. CREATE TABLE IF NOT EXISTS 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 IF NOT EXISTS idx_verifynotice_guild ON verifynotice (
  90. guild
  91. );
  92. CREATE TABLE IF NOT EXISTS 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 IF NOT EXISTS idx_oauthusers_userid ON oauthusers (
  102. userid,
  103. site
  104. );
  105. CREATE TABLE IF NOT EXISTS 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 IF NOT EXISTS idx_rcgcdw_wiki ON rcgcdw (
  124. wiki
  125. );
  126. CREATE INDEX IF NOT EXISTS idx_rcgcdw_webhook ON rcgcdw (
  127. webhook
  128. );
  129. CREATE INDEX IF NOT EXISTS idx_rcgcdw_config ON rcgcdw (
  130. guild,
  131. configid ASC
  132. );
  133. CREATE TABLE IF NOT EXISTS blocklist (
  134. wiki TEXT UNIQUE
  135. NOT NULL,
  136. reason TEXT
  137. );
  138. CREATE INDEX IF NOT EXISTS 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. guild TEXT UNIQUE
  146. NOT NULL
  147. REFERENCES discord (main) ON DELETE CASCADE,
  148. CREATE TABLE IF NOT EXISTS verifynotice (
  149. logchannel TEXT,
  150. onsuccess TEXT,
  151. onmatch TEXT
  152. );
  153. CREATE INDEX IF NOT EXISTS 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 IF NOT EXISTS 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 IF NOT EXISTS 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 IF NOT EXISTS 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. } );