diff options
-rw-r--r-- | server/initializers/constants.ts | 2 | ||||
-rw-r--r-- | server/initializers/database.ts | 6 | ||||
-rw-r--r-- | server/initializers/migrations/0100-activitypub.ts | 212 | ||||
-rw-r--r-- | server/initializers/migrator.ts | 7 | ||||
-rw-r--r-- | server/models/video/video-abuse.ts | 2 |
5 files changed, 224 insertions, 5 deletions
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index e7f668ee4..786334d46 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -14,7 +14,7 @@ import { FollowState } from '../../shared/models/accounts/follow.model' | |||
14 | 14 | ||
15 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
16 | 16 | ||
17 | const LAST_MIGRATION_VERSION = 95 | 17 | const LAST_MIGRATION_VERSION = 100 |
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
diff --git a/server/initializers/database.ts b/server/initializers/database.ts index 5c757694e..9b9a81e26 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts | |||
@@ -31,7 +31,7 @@ const dbname = CONFIG.DATABASE.DBNAME | |||
31 | const username = CONFIG.DATABASE.USERNAME | 31 | const username = CONFIG.DATABASE.USERNAME |
32 | const password = CONFIG.DATABASE.PASSWORD | 32 | const password = CONFIG.DATABASE.PASSWORD |
33 | 33 | ||
34 | const database: { | 34 | export type PeerTubeDatabase = { |
35 | sequelize?: Sequelize.Sequelize, | 35 | sequelize?: Sequelize.Sequelize, |
36 | init?: (silent: boolean) => Promise<void>, | 36 | init?: (silent: boolean) => Promise<void>, |
37 | 37 | ||
@@ -53,7 +53,9 @@ const database: { | |||
53 | BlacklistedVideo?: BlacklistedVideoModel, | 53 | BlacklistedVideo?: BlacklistedVideoModel, |
54 | VideoTag?: VideoTagModel, | 54 | VideoTag?: VideoTagModel, |
55 | Video?: VideoModel | 55 | Video?: VideoModel |
56 | } = {} | 56 | } |
57 | |||
58 | const database: PeerTubeDatabase = {} | ||
57 | 59 | ||
58 | const sequelize = new Sequelize(dbname, username, password, { | 60 | const sequelize = new Sequelize(dbname, username, password, { |
59 | dialect: 'postgres', | 61 | dialect: 'postgres', |
diff --git a/server/initializers/migrations/0100-activitypub.ts b/server/initializers/migrations/0100-activitypub.ts new file mode 100644 index 000000000..50a0adc14 --- /dev/null +++ b/server/initializers/migrations/0100-activitypub.ts | |||
@@ -0,0 +1,212 @@ | |||
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 { JOB_CATEGORIES, SERVER_ACCOUNT_NAME } from '../constants' | ||
8 | import { PeerTubeDatabase } from '../database' | ||
9 | |||
10 | async function up (utils: { | ||
11 | transaction: Sequelize.Transaction, | ||
12 | queryInterface: Sequelize.QueryInterface, | ||
13 | sequelize: Sequelize.Sequelize, | ||
14 | db: PeerTubeDatabase | ||
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 db.Application.findOne() | ||
69 | const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACCOUNT_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 | } | ||
diff --git a/server/initializers/migrator.ts b/server/initializers/migrator.ts index 4fbe1cf5b..187c9be6e 100644 --- a/server/initializers/migrator.ts +++ b/server/initializers/migrator.ts | |||
@@ -26,7 +26,12 @@ async function migrate () { | |||
26 | const migrationScripts = await getMigrationScripts() | 26 | const migrationScripts = await getMigrationScripts() |
27 | 27 | ||
28 | for (const migrationScript of migrationScripts) { | 28 | for (const migrationScript of migrationScripts) { |
29 | await executeMigration(actualVersion, migrationScript) | 29 | try { |
30 | await executeMigration(actualVersion, migrationScript) | ||
31 | } catch (err) { | ||
32 | logger.error('Cannot execute migration %s.', migrationScript.version, err) | ||
33 | process.exit(0) | ||
34 | } | ||
30 | } | 35 | } |
31 | 36 | ||
32 | logger.info('Migrations finished. New migration version schema: %s', LAST_MIGRATION_VERSION) | 37 | logger.info('Migrations finished. New migration version schema: %s', LAST_MIGRATION_VERSION) |
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index e8f4f9a67..d09f5f7a1 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts | |||
@@ -99,7 +99,7 @@ function associate (models) { | |||
99 | VideoAbuse.belongsTo(models.Account, { | 99 | VideoAbuse.belongsTo(models.Account, { |
100 | foreignKey: { | 100 | foreignKey: { |
101 | name: 'reporterAccountId', | 101 | name: 'reporterAccountId', |
102 | allowNull: true | 102 | allowNull: false |
103 | }, | 103 | }, |
104 | onDelete: 'CASCADE' | 104 | onDelete: 'CASCADE' |
105 | }) | 105 | }) |