78bc8101c772d769f361c2c601f022c1d46f0b0a
[oweals/peertube.git] / server / initializers / database.ts
1 import { Sequelize as SequelizeTypescript } from 'sequelize-typescript'
2 import { isTestInstance } from '../helpers/core-utils'
3 import { logger } from '../helpers/logger'
4
5 import { AccountModel } from '../models/account/account'
6 import { AccountVideoRateModel } from '../models/account/account-video-rate'
7 import { UserModel } from '../models/account/user'
8 import { ActorModel } from '../models/activitypub/actor'
9 import { ActorFollowModel } from '../models/activitypub/actor-follow'
10 import { ApplicationModel } from '../models/application/application'
11 import { AvatarModel } from '../models/avatar/avatar'
12 import { OAuthClientModel } from '../models/oauth/oauth-client'
13 import { OAuthTokenModel } from '../models/oauth/oauth-token'
14 import { ServerModel } from '../models/server/server'
15 import { TagModel } from '../models/video/tag'
16 import { VideoModel } from '../models/video/video'
17 import { VideoAbuseModel } from '../models/video/video-abuse'
18 import { VideoBlacklistModel } from '../models/video/video-blacklist'
19 import { VideoChannelModel } from '../models/video/video-channel'
20 import { VideoCommentModel } from '../models/video/video-comment'
21 import { VideoFileModel } from '../models/video/video-file'
22 import { VideoShareModel } from '../models/video/video-share'
23 import { VideoTagModel } from '../models/video/video-tag'
24 import { CONFIG } from './constants'
25 import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update'
26 import { VideoCaptionModel } from '../models/video/video-caption'
27 import { VideoImportModel } from '../models/video/video-import'
28 import { VideoViewModel } from '../models/video/video-views'
29
30 require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string
31
32 const dbname = CONFIG.DATABASE.DBNAME
33 const username = CONFIG.DATABASE.USERNAME
34 const password = CONFIG.DATABASE.PASSWORD
35 const host = CONFIG.DATABASE.HOSTNAME
36 const port = CONFIG.DATABASE.PORT
37 const poolMax = CONFIG.DATABASE.POOL.MAX
38
39 const sequelizeTypescript = new SequelizeTypescript({
40   database: dbname,
41   dialect: 'postgres',
42   host,
43   port,
44   username,
45   password,
46   pool: {
47     max: poolMax
48   },
49   benchmark: isTestInstance(),
50   isolationLevel: SequelizeTypescript.Transaction.ISOLATION_LEVELS.SERIALIZABLE,
51   operatorsAliases: false,
52   logging: (message: string, benchmark: number) => {
53     if (process.env.NODE_DB_LOG === 'false') return
54
55     let newMessage = message
56     if (isTestInstance() === true && benchmark !== undefined) {
57       newMessage += ' | ' + benchmark + 'ms'
58     }
59
60     logger.debug(newMessage)
61   }
62 })
63
64 async function initDatabaseModels (silent: boolean) {
65   sequelizeTypescript.addModels([
66     ApplicationModel,
67     ActorModel,
68     ActorFollowModel,
69     AvatarModel,
70     AccountModel,
71     OAuthClientModel,
72     OAuthTokenModel,
73     ServerModel,
74     TagModel,
75     AccountVideoRateModel,
76     UserModel,
77     VideoAbuseModel,
78     VideoChannelModel,
79     VideoShareModel,
80     VideoFileModel,
81     VideoCaptionModel,
82     VideoBlacklistModel,
83     VideoTagModel,
84     VideoModel,
85     VideoCommentModel,
86     ScheduleVideoUpdateModel,
87     VideoImportModel,
88     VideoViewModel
89   ])
90
91   // Check extensions exist in the database
92   await checkPostgresExtensions()
93
94   // Create custom PostgreSQL functions
95   await createFunctions()
96
97   if (!silent) logger.info('Database %s is ready.', dbname)
98
99   return
100 }
101
102 // ---------------------------------------------------------------------------
103
104 export {
105   initDatabaseModels,
106   sequelizeTypescript
107 }
108
109 // ---------------------------------------------------------------------------
110
111 async function checkPostgresExtensions () {
112   const extensions = [
113     'pg_trgm',
114     'unaccent'
115   ]
116
117   for (const extension of extensions) {
118     const query = `SELECT true AS enabled FROM pg_available_extensions WHERE name = '${extension}' AND installed_version IS NOT NULL;`
119     const [ res ] = await sequelizeTypescript.query(query, { raw: true })
120
121     if (!res || res.length === 0 || res[ 0 ][ 'enabled' ] !== true) {
122       // Try to create the extension ourself
123       try {
124         await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true })
125
126       } catch {
127         const errorMessage = `You need to enable ${extension} extension in PostgreSQL. ` +
128           `You can do so by running 'CREATE EXTENSION ${extension};' as a PostgreSQL super user in ${CONFIG.DATABASE.DBNAME} database.`
129         throw new Error(errorMessage)
130       }
131     }
132   }
133 }
134
135 async function createFunctions () {
136   const query = `CREATE OR REPLACE FUNCTION immutable_unaccent(text)
137   RETURNS text AS
138 $func$
139 SELECT public.unaccent('public.unaccent', $1::text)
140 $func$  LANGUAGE sql IMMUTABLE;`
141
142   return sequelizeTypescript.query(query, { raw: true })
143 }