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