From a7d647c4403f8774106f485e8d9323158454e111 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 15 Dec 2017 17:34:38 +0100 Subject: [PATCH] Add dirty migration :/ --- server/controllers/api/videos/index.ts | 10 +- .../migrations/0100-activitypub.ts | 4 +- .../migrations/0130-video-channel-actor.ts | 235 ++++++++++++++++++ server/lib/activitypub/share.ts | 16 +- .../video-file-optimizer-handler.ts | 4 +- 5 files changed, 259 insertions(+), 10 deletions(-) create mode 100644 server/initializers/migrations/0130-video-channel-actor.ts diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index d6934748f..3e65e844b 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -21,7 +21,11 @@ import { VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' -import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServer } from '../../../lib/activitypub' +import { + fetchRemoteVideoDescription, + getVideoActivityPubUrl, + shareVideoByServerAndChannel +} from '../../../lib/activitypub' import { sendCreateVideo, sendCreateViewToOrigin, sendCreateViewToVideoFollowers, sendUpdateVideo } from '../../../lib/activitypub/send' import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler' import { @@ -249,7 +253,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi await sendCreateVideo(video, t) // TODO: share by video channel - await shareVideoByServer(video, t) + await shareVideoByServerAndChannel(video, t) logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) @@ -306,7 +310,7 @@ async function updateVideo (req: express.Request, res: express.Response) { if (wasPrivateVideo === true && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE) { await sendCreateVideo(videoInstanceUpdated, t) // TODO: Send by video channel - await shareVideoByServer(videoInstanceUpdated, t) + await shareVideoByServerAndChannel(videoInstanceUpdated, t) } }) diff --git a/server/initializers/migrations/0100-activitypub.ts b/server/initializers/migrations/0100-activitypub.ts index d896b3205..8c5198f85 100644 --- a/server/initializers/migrations/0100-activitypub.ts +++ b/server/initializers/migrations/0100-activitypub.ts @@ -1,7 +1,7 @@ import { values } from 'lodash' import * as Sequelize from 'sequelize' import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' -import { shareVideoByServer } from '../../lib/activitypub/share' +import { shareVideoByServerAndChannel } from '../../lib/activitypub/share' import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url' import { createLocalAccountWithoutKeys } from '../../lib/user' import { ApplicationModel } from '../../models/application/application' @@ -197,7 +197,7 @@ async function up (utils: { }) for (const video of videos) { - await shareVideoByServer(video, undefined) + await shareVideoByServerAndChannel(video, undefined) } } } diff --git a/server/initializers/migrations/0130-video-channel-actor.ts b/server/initializers/migrations/0130-video-channel-actor.ts new file mode 100644 index 000000000..72a0daef7 --- /dev/null +++ b/server/initializers/migrations/0130-video-channel-actor.ts @@ -0,0 +1,235 @@ +import * as Sequelize from 'sequelize' +import { DataType } from 'sequelize-typescript' + +async function up (utils: { + transaction: Sequelize.Transaction, + queryInterface: Sequelize.QueryInterface, + sequelize: Sequelize.Sequelize +}): Promise { + // Create actor table + { + const queries = [ + ` + CREATE TYPE enum_actor_type AS ENUM ( + 'Group', + 'Person', + 'Application' + ) + `, + ` + CREATE TABLE actor ( + id integer NOT NULL, + type enum_actor_type NOT NULL, + uuid uuid NOT NULL, + name character varying(255) NOT NULL, + url character varying(2000) NOT NULL, + "publicKey" character varying(5000), + "privateKey" character varying(5000), + "followersCount" integer NOT NULL, + "followingCount" integer NOT NULL, + "inboxUrl" character varying(2000) NOT NULL, + "outboxUrl" character varying(2000) NOT NULL, + "sharedInboxUrl" character varying(2000) NOT NULL, + "followersUrl" character varying(2000) NOT NULL, + "followingUrl" character varying(2000) NOT NULL, + "avatarId" integer, + "serverId" integer, + "createdAt" timestamp with time zone NOT NULL, + "updatedAt" timestamp with time zone NOT NULL + );`, + ` + CREATE SEQUENCE actor_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 + `, + `ALTER SEQUENCE actor_id_seq OWNED BY actor.id`, + `ALTER TABLE ONLY actor ALTER COLUMN id SET DEFAULT nextval('actor_id_seq'::regclass)`, + `ALTER TABLE ONLY actor ADD CONSTRAINT actor_pkey PRIMARY KEY (id);`, + `CREATE UNIQUE INDEX actor_name_server_id ON actor USING btree (name, "serverId")`, + `ALTER TABLE ONLY actor + ADD CONSTRAINT "actor_avatarId_fkey" FOREIGN KEY ("avatarId") REFERENCES avatar(id) ON UPDATE CASCADE ON DELETE CASCADE`, + `ALTER TABLE ONLY actor + ADD CONSTRAINT "actor_serverId_fkey" FOREIGN KEY ("serverId") REFERENCES server(id) ON UPDATE CASCADE ON DELETE CASCADE;` + ] + + for (const query of queries) { + await utils.sequelize.query(query) + } + } + + { + // tslint:disable:no-trailing-whitespace + const query1 = + ` + INSERT INTO "actor" + ( + type, uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", + "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" + ) + SELECT + 'Application', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", + "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" + FROM account + WHERE "applicationId" IS NOT NULL + ` + await utils.sequelize.query(query1) + + const query2 = + ` + INSERT INTO "actor" + ( + type, uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", + "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" + ) + SELECT + 'Person', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", + "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" + FROM account + WHERE "applicationId" IS NULL + ` + await utils.sequelize.query(query2) + } + + { + const data = { + type: DataType.INTEGER, + allowNull: true, + defaultValue: null + } + await utils.queryInterface.addColumn('account', 'actorId', data) + + const query1 = `UPDATE account SET "actorId" = (SELECT id FROM actor WHERE actor.url = account.url)` + await utils.sequelize.query(query1) + + data.allowNull = false + await utils.queryInterface.changeColumn('account', 'actorId', data) + + const query2 = `ALTER TABLE ONLY account + ADD CONSTRAINT "account_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES actor(id) ON UPDATE CASCADE ON DELETE CASCADE; + ` + await utils.sequelize.query(query2) + } + + { + const query = ` + INSERT INTO actor + ( + type, uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", + "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" + ) + SELECT + 'Group', "videoChannel".uuid, "videoChannel".uuid, "videoChannel".url, null, null, 0, 0, "videoChannel".url || '/inbox', + "videoChannel".url || '/outbox', "videoChannel".url || '/inbox', "videoChannel".url || '/followers', "videoChannel".url || '/following', + null, account."serverId", "videoChannel"."createdAt", "videoChannel"."updatedAt" + FROM "videoChannel" + INNER JOIN "account" on "videoChannel"."accountId" = "account".id + ` + await utils.sequelize.query(query) + } + + { + const data = { + type: DataType.INTEGER, + allowNull: true, + defaultValue: null + } + await utils.queryInterface.addColumn('videoChannel', 'actorId', data) + + const query1 = `UPDATE "videoChannel" SET "actorId" = (SELECT id FROM actor WHERE actor.url = "videoChannel".url)` + await utils.sequelize.query(query1) + + data.allowNull = false + await utils.queryInterface.changeColumn('videoChannel', 'actorId', data) + + const query2 = ` + ALTER TABLE ONLY "videoChannel" + ADD CONSTRAINT "videoChannel_actorId_fkey" FOREIGN KEY ("actorId") REFERENCES actor(id) ON UPDATE CASCADE ON DELETE CASCADE; + ` + await utils.sequelize.query(query2) + } + + { + await utils.queryInterface.renameTable('accountFollow', 'actorFollow') + await utils.queryInterface.renameColumn('actorFollow', 'accountId', 'actorId') + await utils.queryInterface.renameColumn('actorFollow', 'targetAccountId', 'targetActorId') + + try { + await utils.queryInterface.removeConstraint('actorFollow', 'AccountFollows_accountId_fkey') + await utils.queryInterface.removeConstraint('actorFollow', 'AccountFollows_targetAccountId_fkey') + } catch { + await utils.queryInterface.removeConstraint('actorFollow', 'accountFollow_accountId_fkey') + await utils.queryInterface.removeConstraint('actorFollow', 'accountFollow_targetAccountId_fkey') + } + + const query1 = `UPDATE "actorFollow" + SET "actorId" = + (SELECT "actorId" FROM account WHERE id = "actorFollow"."actorId")` + await utils.sequelize.query(query1) + + const query2 = `UPDATE "actorFollow" + SET "targetActorId" = + (SELECT "actorId" FROM account WHERE id = "actorFollow"."actorId")` + + await utils.sequelize.query(query2) + } + + { + await utils.queryInterface.renameColumn('videoShare', 'accountId', 'actorId') + + try { + await utils.queryInterface.removeConstraint('videoShare', 'VideoShares_accountId_fkey') + } catch { + await utils.queryInterface.removeConstraint('videoShare', 'videoShare_accountId_fkey') + } + + const query = `UPDATE "videoShare" + SET "actorId" = + (SELECT "actorId" FROM account WHERE id = "videoShare"."actorId")` + await utils.sequelize.query(query) + } + + { + const columnsToDelete = [ + 'uuid', + 'url', + 'publicKey', + 'privateKey', + 'followersCount', + 'followingCount', + 'inboxUrl', + 'outboxUrl', + 'sharedInboxUrl', + 'followersUrl', + 'followingUrl', + 'serverId', + 'avatarId' + ] + for (const columnToDelete of columnsToDelete) { + await utils.queryInterface.removeColumn('account', columnToDelete) + } + } + + { + const columnsToDelete = [ + 'uuid', + 'remote', + 'url' + ] + for (const columnToDelete of columnsToDelete) { + await utils.queryInterface.removeColumn('videoChannel', columnToDelete) + } + } +} + +function down (options) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts index f79c4e532..fb01368ec 100644 --- a/server/lib/activitypub/share.ts +++ b/server/lib/activitypub/share.ts @@ -4,17 +4,27 @@ import { VideoModel } from '../../models/video/video' import { VideoShareModel } from '../../models/video/video-share' import { sendVideoAnnounceToFollowers } from './send' -async function shareVideoByServer (video: VideoModel, t: Transaction) { +async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) { const serverActor = await getServerActor() - await VideoShareModel.create({ + const serverShare = VideoShareModel.create({ actorId: serverActor.id, videoId: video.id }, { transaction: t }) + const videoChannelShare = VideoShareModel.create({ + actorId: video.VideoChannel.actorId, + videoId: video.id + }, { transaction: t }) + + await Promise.all([ + serverShare, + videoChannelShare + ]) + return sendVideoAnnounceToFollowers(serverActor, video, t) } export { - shareVideoByServer + shareVideoByServerAndChannel } diff --git a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts b/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts index 7df048006..cde4948de 100644 --- a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts +++ b/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts @@ -2,7 +2,7 @@ import * as Bluebird from 'bluebird' import { computeResolutionsToTranscode, logger } from '../../../helpers' import { sequelizeTypescript } from '../../../initializers' import { VideoModel } from '../../../models/video/video' -import { shareVideoByServer } from '../../activitypub' +import { shareVideoByServerAndChannel } from '../../activitypub' import { sendCreateVideo } from '../../activitypub/send' import { JobScheduler } from '../job-scheduler' import { TranscodingJobPayload } from './transcoding-job-scheduler' @@ -38,7 +38,7 @@ async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobSch // Now we'll add the video's meta data to our followers await sendCreateVideo(video, undefined) // TODO: share by channel - await shareVideoByServer(video, undefined) + await shareVideoByServerAndChannel(video, undefined) const originalFileHeight = await videoDatabase.getOriginalFileHeight()