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