--- /dev/null
+import { values } from 'lodash'
+import * as Sequelize from 'sequelize'
+import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
+import { shareVideoByServer } from '../../lib/activitypub/share'
+import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url'
+import { createLocalAccountWithoutKeys } from '../../lib/user'
+import { JOB_CATEGORIES, SERVER_ACCOUNT_NAME } from '../constants'
+import { PeerTubeDatabase } from '../database'
+
+async function up (utils: {
+ transaction: Sequelize.Transaction,
+ queryInterface: Sequelize.QueryInterface,
+ sequelize: Sequelize.Sequelize,
+ db: PeerTubeDatabase
+}): Promise<void> {
+ const q = utils.queryInterface
+ const db = utils.db
+
+ // Assert there are no friends
+ {
+ const query = 'SELECT COUNT(*) as total FROM "Pods"'
+ const options = {
+ type: Sequelize.QueryTypes.SELECT
+ }
+ const res = await utils.sequelize.query(query, options)
+
+ if (!res[0] || res[0].total !== 0) {
+ throw new Error('You need to quit friends.')
+ }
+ }
+
+ // Pods -> Servers
+ await utils.queryInterface.renameTable('Pods', 'Servers')
+
+ // Create Account table
+ await db.Account.sync()
+
+ // Create AccountFollows table
+ await db.AccountFollow.sync()
+
+ // Modify video abuse table
+ await db.VideoAbuse.destroy({ truncate: true })
+ await utils.queryInterface.removeColumn('VideoAbuses', 'reporterPodId')
+ await utils.queryInterface.removeColumn('VideoAbuses', 'reporterUsername')
+
+ // Create column link with Account table
+ {
+ const data = {
+ type: Sequelize.INTEGER,
+ allowNull: false,
+ references: {
+ model: 'Accounts',
+ key: 'id'
+ },
+ onDelete: 'CASCADE'
+ }
+ await q.addColumn('VideoAbuses', 'reporterAccountId', data)
+ }
+
+ // Drop request tables
+ await utils.queryInterface.dropTable('RequestToPods')
+ await utils.queryInterface.dropTable('RequestVideoEvents')
+ await utils.queryInterface.dropTable('RequestVideoQadus')
+ await utils.queryInterface.dropTable('Requests')
+
+ // Create application account
+ {
+ const applicationInstance = await db.Application.findOne()
+ const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACCOUNT_NAME, null, applicationInstance.id, undefined)
+
+ const { publicKey, privateKey } = await createPrivateAndPublicKeys()
+ accountCreated.set('publicKey', publicKey)
+ accountCreated.set('privateKey', privateKey)
+
+ await accountCreated.save()
+ }
+
+ // Drop old video channel foreign key (referencing Authors)
+ {
+ const query = 'ALTER TABLE "VideoChannels" DROP CONSTRAINT "VideoChannels_authorId_fkey"'
+ await utils.sequelize.query(query)
+ }
+
+ // Recreate accounts for each user
+ const users = await db.User.findAll()
+ for (const user of users) {
+ const account = await createLocalAccountWithoutKeys(user.username, user.id, null, undefined)
+
+ const { publicKey, privateKey } = await createPrivateAndPublicKeys()
+ account.set('publicKey', publicKey)
+ account.set('privateKey', privateKey)
+ await account.save()
+ }
+
+ {
+ const data = {
+ type: Sequelize.INTEGER,
+ allowNull: true,
+ onDelete: 'CASCADE',
+ reference: {
+ model: 'Account',
+ key: 'id'
+ }
+ }
+ await q.addColumn('VideoChannels', 'accountId', data)
+
+ {
+ const query = 'UPDATE "VideoChannels" SET "accountId" = ' +
+ '(SELECT "Accounts"."id" FROM "Accounts" INNER JOIN "Authors" ON "Authors"."userId" = "Accounts"."userId" ' +
+ 'WHERE "VideoChannels"."authorId" = "Authors"."id")'
+ await utils.sequelize.query(query)
+ }
+
+ data.allowNull = false
+ await q.changeColumn('VideoChannels', 'accountId', data)
+
+ await q.removeColumn('VideoChannels', 'authorId')
+ }
+
+ // Add url column to "Videos"
+ {
+ const data = {
+ type: Sequelize.STRING,
+ defaultValue: null,
+ allowNull: true
+ }
+ await q.addColumn('Videos', 'url', data)
+
+ const videos = await db.Video.findAll()
+ for (const video of videos) {
+ video.url = getVideoActivityPubUrl(video)
+ await video.save()
+ }
+
+ data.allowNull = false
+ await q.changeColumn('Videos', 'url', data)
+ }
+
+ // Add url column to "VideoChannels"
+ {
+ const data = {
+ type: Sequelize.STRING,
+ defaultValue: null,
+ allowNull: true
+ }
+ await q.addColumn('VideoChannels', 'url', data)
+
+ const videoChannels = await db.VideoChannel.findAll()
+ for (const videoChannel of videoChannels) {
+ videoChannel.url = getVideoChannelActivityPubUrl(videoChannel)
+ await videoChannel.save()
+ }
+
+ data.allowNull = false
+ await q.changeColumn('VideoChannels', 'url', data)
+ }
+
+ // Loss old video rates, whatever
+ await utils.queryInterface.dropTable('UserVideoRates')
+ await db.AccountVideoRate.sync()
+
+ {
+ const data = {
+ type: Sequelize.ENUM(values(JOB_CATEGORIES)),
+ defaultValue: 'transcoding',
+ allowNull: false
+ }
+ await q.addColumn('Jobs', 'category', data)
+ }
+
+ await db.VideoShare.sync()
+ await db.VideoChannelShare.sync()
+
+ {
+ const videos = await db.Video.findAll({
+ include: [
+ {
+ model: db.Video['sequelize'].models.VideoChannel,
+ include: [
+ {
+ model: db.Video['sequelize'].models.Account,
+ include: [ { model: db.Video['sequelize'].models.Server, required: false } ]
+ }
+ ]
+ },
+ {
+ model: db.Video['sequelize'].models.AccountVideoRate,
+ include: [ db.Video['sequelize'].models.Account ]
+ },
+ {
+ model: db.Video['sequelize'].models.VideoShare,
+ include: [ db.Video['sequelize'].models.Account ]
+ },
+ db.Video['sequelize'].models.Tag,
+ db.Video['sequelize'].models.VideoFile
+ ]
+ })
+
+ for (const video of videos) {
+ await shareVideoByServer(video, undefined)
+ }
+ }
+}
+
+function down (options) {
+ throw new Error('Not implemented.')
+}
+
+export {
+ up,
+ down
+}