]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/redis.ts
Move config in its own file
[github/Chocobozzz/PeerTube.git] / server / lib / redis.ts
CommitLineData
4195cd2b 1import * as express from 'express'
ecb4e35f
C
2import { createClient, RedisClient } from 'redis'
3import { logger } from '../helpers/logger'
4import { generateRandomString } from '../helpers/utils'
a4101923 5import {
a4101923
C
6 CONTACT_FORM_LIFETIME,
7 USER_EMAIL_VERIFY_LIFETIME,
8 USER_PASSWORD_RESET_LIFETIME,
6dd9de95
C
9 VIDEO_VIEW_LIFETIME,
10 WEBSERVER
a4101923 11} from '../initializers'
6dd9de95 12import { CONFIG } from '../initializers/config'
4195cd2b
C
13
14type CachedRoute = {
15 body: string,
2cebd797
C
16 contentType?: string
17 statusCode?: string
4195cd2b 18}
ecb4e35f
C
19
20class Redis {
21
22 private static instance: Redis
23 private initialized = false
24 private client: RedisClient
25 private prefix: string
26
27 private constructor () {}
28
29 init () {
30 // Already initialized
31 if (this.initialized === true) return
32 this.initialized = true
33
19f7b248 34 this.client = createClient(Redis.getRedisClient())
ecb4e35f
C
35
36 this.client.on('error', err => {
d5b7d911 37 logger.error('Error in Redis client.', { err })
ecb4e35f
C
38 process.exit(-1)
39 })
40
41 if (CONFIG.REDIS.AUTH) {
42 this.client.auth(CONFIG.REDIS.AUTH)
43 }
44
6dd9de95 45 this.prefix = 'redis-' + WEBSERVER.HOST + '-'
ecb4e35f
C
46 }
47
19f7b248
RK
48 static getRedisClient () {
49 return Object.assign({},
50 (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {},
51 (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {},
52 (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT) ?
53 { host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT } :
54 { path: CONFIG.REDIS.SOCKET }
55 )
56 }
57
6e46de09
C
58 /************* Forgot password *************/
59
ecb4e35f
C
60 async setResetPasswordVerificationString (userId: number) {
61 const generatedString = await generateRandomString(32)
62
63 await this.setValue(this.generateResetPasswordKey(userId), generatedString, USER_PASSWORD_RESET_LIFETIME)
64
65 return generatedString
66 }
67
68 async getResetPasswordLink (userId: number) {
69 return this.getValue(this.generateResetPasswordKey(userId))
70 }
71
6e46de09
C
72 /************* Email verification *************/
73
d9eaee39
JM
74 async setVerifyEmailVerificationString (userId: number) {
75 const generatedString = await generateRandomString(32)
76
77 await this.setValue(this.generateVerifyEmailKey(userId), generatedString, USER_EMAIL_VERIFY_LIFETIME)
78
79 return generatedString
80 }
81
82 async getVerifyEmailLink (userId: number) {
83 return this.getValue(this.generateVerifyEmailKey(userId))
84 }
85
a4101923
C
86 /************* Contact form per IP *************/
87
88 async setContactFormIp (ip: string) {
89 return this.setValue(this.generateContactFormKey(ip), '1', CONTACT_FORM_LIFETIME)
90 }
91
0f6acda1 92 async doesContactFormIpExist (ip: string) {
a4101923
C
93 return this.exists(this.generateContactFormKey(ip))
94 }
95
6e46de09
C
96 /************* Views per IP *************/
97
6b616860 98 setIPVideoView (ip: string, videoUUID: string) {
6e46de09 99 return this.setValue(this.generateViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME)
b5c0e955
C
100 }
101
0f6acda1 102 async doesVideoIPViewExist (ip: string, videoUUID: string) {
6e46de09 103 return this.exists(this.generateViewKey(ip, videoUUID))
b5c0e955
C
104 }
105
6e46de09
C
106 /************* API cache *************/
107
4195cd2b 108 async getCachedRoute (req: express.Request) {
6e46de09 109 const cached = await this.getObject(this.generateCachedRouteKey(req))
4195cd2b
C
110
111 return cached as CachedRoute
112 }
113
fd4484f1 114 setCachedRoute (req: express.Request, body: any, lifetime: number, contentType?: string, statusCode?: number) {
c1e791ba
RK
115 const cached: CachedRoute = Object.assign({}, {
116 body: body.toString()
117 },
118 (contentType) ? { contentType } : null,
119 (statusCode) ? { statusCode: statusCode.toString() } : null
120 )
4195cd2b 121
6e46de09 122 return this.setObject(this.generateCachedRouteKey(req), cached, lifetime)
4195cd2b
C
123 }
124
6e46de09
C
125 /************* Video views *************/
126
6b616860
C
127 addVideoView (videoId: number) {
128 const keyIncr = this.generateVideoViewKey(videoId)
129 const keySet = this.generateVideosViewKey()
130
131 return Promise.all([
132 this.addToSet(keySet, videoId.toString()),
133 this.increment(keyIncr)
134 ])
135 }
136
137 async getVideoViews (videoId: number, hour: number) {
138 const key = this.generateVideoViewKey(videoId, hour)
139
140 const valueString = await this.getValue(key)
6040f87d
C
141 const valueInt = parseInt(valueString, 10)
142
143 if (isNaN(valueInt)) {
144 logger.error('Cannot get videos views of video %d in hour %d: views number is NaN (%s).', videoId, hour, valueString)
145 return undefined
146 }
147
148 return valueInt
6b616860
C
149 }
150
151 async getVideosIdViewed (hour: number) {
152 const key = this.generateVideosViewKey(hour)
153
154 const stringIds = await this.getSet(key)
155 return stringIds.map(s => parseInt(s, 10))
156 }
157
158 deleteVideoViews (videoId: number, hour: number) {
159 const keySet = this.generateVideosViewKey(hour)
160 const keyIncr = this.generateVideoViewKey(videoId, hour)
161
162 return Promise.all([
163 this.deleteFromSet(keySet, videoId.toString()),
164 this.deleteKey(keyIncr)
165 ])
166 }
167
6e46de09
C
168 /************* Keys generation *************/
169
170 generateCachedRouteKey (req: express.Request) {
171 return req.method + '-' + req.originalUrl
172 }
173
174 private generateVideosViewKey (hour?: number) {
6b616860
C
175 if (!hour) hour = new Date().getHours()
176
177 return `videos-view-h${hour}`
178 }
179
6e46de09 180 private generateVideoViewKey (videoId: number, hour?: number) {
6b616860
C
181 if (!hour) hour = new Date().getHours()
182
183 return `video-view-${videoId}-h${hour}`
184 }
185
6e46de09 186 private generateResetPasswordKey (userId: number) {
b40f0575
C
187 return 'reset-password-' + userId
188 }
189
6e46de09 190 private generateVerifyEmailKey (userId: number) {
d9eaee39
JM
191 return 'verify-email-' + userId
192 }
193
6e46de09 194 private generateViewKey (ip: string, videoUUID: string) {
a4101923
C
195 return `views-${videoUUID}-${ip}`
196 }
197
198 private generateContactFormKey (ip: string) {
199 return 'contact-form-' + ip
b40f0575
C
200 }
201
6e46de09 202 /************* Redis helpers *************/
b40f0575 203
ecb4e35f
C
204 private getValue (key: string) {
205 return new Promise<string>((res, rej) => {
206 this.client.get(this.prefix + key, (err, value) => {
207 if (err) return rej(err)
208
209 return res(value)
210 })
211 })
212 }
213
6b616860
C
214 private getSet (key: string) {
215 return new Promise<string[]>((res, rej) => {
216 this.client.smembers(this.prefix + key, (err, value) => {
217 if (err) return rej(err)
218
219 return res(value)
220 })
221 })
222 }
223
224 private addToSet (key: string, value: string) {
225 return new Promise<string[]>((res, rej) => {
226 this.client.sadd(this.prefix + key, value, err => err ? rej(err) : res())
227 })
228 }
229
230 private deleteFromSet (key: string, value: string) {
231 return new Promise<void>((res, rej) => {
232 this.client.srem(this.prefix + key, value, err => err ? rej(err) : res())
233 })
234 }
235
236 private deleteKey (key: string) {
237 return new Promise<void>((res, rej) => {
238 this.client.del(this.prefix + key, err => err ? rej(err) : res())
239 })
240 }
241
6e46de09
C
242 private deleteFieldInHash (key: string, field: string) {
243 return new Promise<void>((res, rej) => {
244 this.client.hdel(this.prefix + key, field, err => err ? rej(err) : res())
245 })
246 }
247
ecb4e35f
C
248 private setValue (key: string, value: string, expirationMilliseconds: number) {
249 return new Promise<void>((res, rej) => {
250 this.client.set(this.prefix + key, value, 'PX', expirationMilliseconds, (err, ok) => {
251 if (err) return rej(err)
252
4195cd2b 253 if (ok !== 'OK') return rej(new Error('Redis set result is not OK.'))
ecb4e35f
C
254
255 return res()
256 })
257 })
258 }
259
4195cd2b
C
260 private setObject (key: string, obj: { [ id: string ]: string }, expirationMilliseconds: number) {
261 return new Promise<void>((res, rej) => {
262 this.client.hmset(this.prefix + key, obj, (err, ok) => {
263 if (err) return rej(err)
264 if (!ok) return rej(new Error('Redis mset result is not OK.'))
265
266 this.client.pexpire(this.prefix + key, expirationMilliseconds, (err, ok) => {
267 if (err) return rej(err)
268 if (!ok) return rej(new Error('Redis expiration result is not OK.'))
269
270 return res()
271 })
272 })
273 })
274 }
275
276 private getObject (key: string) {
277 return new Promise<{ [ id: string ]: string }>((res, rej) => {
278 this.client.hgetall(this.prefix + key, (err, value) => {
279 if (err) return rej(err)
280
281 return res(value)
282 })
283 })
284 }
285
6e46de09
C
286 private setValueInHash (key: string, field: string, value: string) {
287 return new Promise<void>((res, rej) => {
288 this.client.hset(this.prefix + key, field, value, (err) => {
289 if (err) return rej(err)
290
291 return res()
292 })
293 })
294 }
295
6b616860
C
296 private increment (key: string) {
297 return new Promise<number>((res, rej) => {
298 this.client.incr(this.prefix + key, (err, value) => {
299 if (err) return rej(err)
300
301 return res(value)
302 })
303 })
304 }
305
b5c0e955
C
306 private exists (key: string) {
307 return new Promise<boolean>((res, rej) => {
308 this.client.exists(this.prefix + key, (err, existsNumber) => {
309 if (err) return rej(err)
310
311 return res(existsNumber === 1)
312 })
313 })
314 }
315
ecb4e35f
C
316 static get Instance () {
317 return this.instance || (this.instance = new this())
318 }
319}
320
321// ---------------------------------------------------------------------------
322
323export {
324 Redis
325}