(hotfix) remove null categories in RSS/Atom feeds
[oweals/peertube.git] / server / initializers / migrations / 0100-activitypub.ts
1 import * as Sequelize from 'sequelize'
2 import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
3 import { shareVideoByServerAndChannel } from '../../lib/activitypub/share'
4 import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url'
5 import { createLocalAccountWithoutKeys } from '../../lib/user'
6 import { ApplicationModel } from '../../models/application/application'
7 import { SERVER_ACTOR_NAME } from '../constants'
8
9 async function up (utils: {
10   transaction: Sequelize.Transaction,
11   queryInterface: Sequelize.QueryInterface,
12   sequelize: Sequelize.Sequelize,
13   db: any
14 }): Promise<void> {
15   const q = utils.queryInterface
16   const db = utils.db
17
18   // Assert there are no friends
19   {
20     const query = 'SELECT COUNT(*) as total FROM "Pods"'
21     const options = {
22       type: Sequelize.QueryTypes.SELECT
23     }
24     const res = await utils.sequelize.query(query, options) as any
25
26     if (!res[0] || res[0].total !== 0) {
27       throw new Error('You need to quit friends.')
28     }
29   }
30
31   // Pods -> Servers
32   await utils.queryInterface.renameTable('Pods', 'Servers')
33
34   // Create Account table
35   await db.Account.sync()
36
37   // Create AccountFollows table
38   await db.AccountFollow.sync()
39
40   // Modify video abuse table
41   await db.VideoAbuse.destroy({ truncate: true })
42   await utils.queryInterface.removeColumn('VideoAbuses', 'reporterPodId')
43   await utils.queryInterface.removeColumn('VideoAbuses', 'reporterUsername')
44
45   // Create column link with Account table
46   {
47     const data = {
48       type: Sequelize.INTEGER,
49       allowNull: false,
50       references: {
51         model: 'Accounts',
52         key: 'id'
53       },
54       onDelete: 'CASCADE'
55     }
56     await q.addColumn('VideoAbuses', 'reporterAccountId', data)
57   }
58
59   // Drop request tables
60   await utils.queryInterface.dropTable('RequestToPods')
61   await utils.queryInterface.dropTable('RequestVideoEvents')
62   await utils.queryInterface.dropTable('RequestVideoQadus')
63   await utils.queryInterface.dropTable('Requests')
64
65   // Create application account
66   {
67     const applicationInstance = await ApplicationModel.findOne()
68     const accountCreated = await createLocalAccountWithoutKeys({
69       name: SERVER_ACTOR_NAME,
70       userId: null,
71       applicationId: applicationInstance.id,
72       t: undefined
73     })
74
75     const { publicKey, privateKey } = await createPrivateAndPublicKeys()
76     accountCreated.Actor.publicKey = publicKey
77     accountCreated.Actor.privateKey = privateKey
78
79     await accountCreated.save()
80   }
81
82   // Drop old video channel foreign key (referencing Authors)
83   {
84     const query = 'ALTER TABLE "VideoChannels" DROP CONSTRAINT "VideoChannels_authorId_fkey"'
85     await utils.sequelize.query(query)
86   }
87
88   // Recreate accounts for each user
89   const users = await db.User.findAll()
90   for (const user of users) {
91     const account = await createLocalAccountWithoutKeys({ name: user.username, userId: user.id, applicationId: null, t: undefined })
92
93     const { publicKey, privateKey } = await createPrivateAndPublicKeys()
94     account.Actor.publicKey = publicKey
95     account.Actor.privateKey = privateKey
96     await account.save()
97   }
98
99   {
100     const data = {
101       type: Sequelize.INTEGER,
102       allowNull: true,
103       onDelete: 'CASCADE',
104       reference: {
105         model: 'Account',
106         key: 'id'
107       }
108     }
109     await q.addColumn('VideoChannels', 'accountId', data)
110
111     {
112       const query = 'UPDATE "VideoChannels" SET "accountId" = ' +
113                     '(SELECT "Accounts"."id" FROM "Accounts" INNER JOIN "Authors" ON "Authors"."userId" = "Accounts"."userId" ' +
114                     'WHERE "VideoChannels"."authorId" = "Authors"."id")'
115       await utils.sequelize.query(query)
116     }
117
118     data.allowNull = false
119     await q.changeColumn('VideoChannels', 'accountId', data)
120
121     await q.removeColumn('VideoChannels', 'authorId')
122   }
123
124   // Add url column to "Videos"
125   {
126     const data = {
127       type: Sequelize.STRING,
128       defaultValue: null,
129       allowNull: true
130     }
131     await q.addColumn('Videos', 'url', data)
132
133     const videos = await db.Video.findAll()
134     for (const video of videos) {
135       video.url = getVideoActivityPubUrl(video)
136       await video.save()
137     }
138
139     data.allowNull = false
140     await q.changeColumn('Videos', 'url', data)
141   }
142
143   // Add url column to "VideoChannels"
144   {
145     const data = {
146       type: Sequelize.STRING,
147       defaultValue: null,
148       allowNull: true
149     }
150     await q.addColumn('VideoChannels', 'url', data)
151
152     const videoChannels = await db.VideoChannel.findAll()
153     for (const videoChannel of videoChannels) {
154       videoChannel.url = getVideoChannelActivityPubUrl(videoChannel)
155       await videoChannel.save()
156     }
157
158     data.allowNull = false
159     await q.changeColumn('VideoChannels', 'url', data)
160   }
161
162   // Loss old video rates, whatever
163   await utils.queryInterface.dropTable('UserVideoRates')
164   await db.AccountVideoRate.sync()
165
166   {
167     const data = {
168       type: Sequelize.ENUM('transcoding', 'activitypub-http'),
169       defaultValue: 'transcoding',
170       allowNull: false
171     }
172     await q.addColumn('Jobs', 'category', data)
173   }
174
175   await db.VideoShare.sync()
176   await db.VideoChannelShare.sync()
177
178   {
179     const videos = await db.Video.findAll({
180       include: [
181         {
182           model: db.Video['sequelize'].models.VideoChannel,
183           include: [
184             {
185               model: db.Video['sequelize'].models.Account,
186               include: [ { model: db.Video['sequelize'].models.Server, required: false } ]
187             }
188           ]
189         },
190         {
191           model: db.Video['sequelize'].models.AccountVideoRate,
192           include: [ db.Video['sequelize'].models.Account ]
193         },
194         {
195           model: db.Video['sequelize'].models.VideoShare,
196           include: [ db.Video['sequelize'].models.Account ]
197         },
198         db.Video['sequelize'].models.Tag,
199         db.Video['sequelize'].models.VideoFile
200       ]
201     })
202
203     for (const video of videos) {
204       await shareVideoByServerAndChannel(video, undefined)
205     }
206   }
207 }
208
209 function down (options) {
210   throw new Error('Not implemented.')
211 }
212
213 export {
214   up,
215   down
216 }