Add comments controller
[oweals/peertube.git] / server / models / video / video-comment.ts
1 import * as Sequelize from 'sequelize'
2 import {
3   AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, IFindOptions, Is, Model, Scopes, Table,
4   UpdatedAt
5 } from 'sequelize-typescript'
6 import { VideoComment } from '../../../shared/models/videos/video-comment.model'
7 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub'
8 import { CONSTRAINTS_FIELDS } from '../../initializers'
9 import { ActorModel } from '../activitypub/actor'
10 import { getSort, throwIfNotValid } from '../utils'
11 import { VideoModel } from './video'
12
13 enum ScopeNames {
14   WITH_ACTOR = 'WITH_ACTOR'
15 }
16
17 @Scopes({
18   [ScopeNames.WITH_ACTOR]: {
19     include: [
20       () => ActorModel
21     ]
22   }
23 })
24 @Table({
25   tableName: 'videoComment',
26   indexes: [
27     {
28       fields: [ 'videoId' ]
29     },
30     {
31       fields: [ 'videoId', 'originCommentId' ]
32     }
33   ]
34 })
35 export class VideoCommentModel extends Model<VideoCommentModel> {
36   @CreatedAt
37   createdAt: Date
38
39   @UpdatedAt
40   updatedAt: Date
41
42   @AllowNull(false)
43   @Is('VideoCommentUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
44   @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max))
45   url: string
46
47   @AllowNull(false)
48   @Column(DataType.TEXT)
49   text: string
50
51   @ForeignKey(() => VideoCommentModel)
52   @Column
53   originCommentId: number
54
55   @BelongsTo(() => VideoCommentModel, {
56     foreignKey: {
57       allowNull: true
58     },
59     onDelete: 'CASCADE'
60   })
61   OriginVideoComment: VideoCommentModel
62
63   @ForeignKey(() => VideoCommentModel)
64   @Column
65   inReplyToCommentId: number
66
67   @BelongsTo(() => VideoCommentModel, {
68     foreignKey: {
69       allowNull: true
70     },
71     onDelete: 'CASCADE'
72   })
73   InReplyToVideoComment: VideoCommentModel
74
75   @ForeignKey(() => VideoModel)
76   @Column
77   videoId: number
78
79   @BelongsTo(() => VideoModel, {
80     foreignKey: {
81       allowNull: false
82     },
83     onDelete: 'CASCADE'
84   })
85   Video: VideoModel
86
87   @ForeignKey(() => ActorModel)
88   @Column
89   actorId: number
90
91   @BelongsTo(() => ActorModel, {
92     foreignKey: {
93       allowNull: false
94     },
95     onDelete: 'CASCADE'
96   })
97   Actor: ActorModel
98
99   @AfterDestroy
100   static sendDeleteIfOwned (instance: VideoCommentModel) {
101     // TODO
102     return undefined
103   }
104
105   static loadById (id: number, t?: Sequelize.Transaction) {
106     const query: IFindOptions<VideoCommentModel> = {
107       where: {
108         id
109       }
110     }
111
112     if (t !== undefined) query.transaction = t
113
114     return VideoCommentModel.findOne(query)
115   }
116
117   static loadByUrl (url: string, t?: Sequelize.Transaction) {
118     const query: IFindOptions<VideoCommentModel> = {
119       where: {
120         url
121       }
122     }
123
124     if (t !== undefined) query.transaction = t
125
126     return VideoCommentModel.findOne(query)
127   }
128
129   static listThreadsForApi (videoId: number, start: number, count: number, sort: string) {
130     const query = {
131       offset: start,
132       limit: count,
133       order: [ getSort(sort) ],
134       where: {
135         videoId
136       }
137     }
138
139     return VideoCommentModel
140       .scope([ ScopeNames.WITH_ACTOR ])
141       .findAndCountAll(query)
142       .then(({ rows, count }) => {
143         return { total: count, data: rows }
144       })
145   }
146
147   static listThreadCommentsForApi (videoId: number, threadId: number) {
148     const query = {
149       order: [ 'id', 'ASC' ],
150       where: {
151         videoId,
152         [ Sequelize.Op.or ]: [
153           { id: threadId },
154           { originCommentId: threadId }
155         ]
156       }
157     }
158
159     return VideoCommentModel
160       .scope([ ScopeNames.WITH_ACTOR ])
161       .findAndCountAll(query)
162       .then(({ rows, count }) => {
163         return { total: count, data: rows }
164       })
165   }
166
167   toFormattedJSON () {
168     return {
169       id: this.id,
170       url: this.url,
171       text: this.text,
172       threadId: this.originCommentId || this.id,
173       inReplyToCommentId: this.inReplyToCommentId,
174       videoId: this.videoId,
175       createdAt: this.createdAt,
176       updatedAt: this.updatedAt
177     } as VideoComment
178   }
179 }