mirror of https://github.com/Chocobozzz/PeerTube
Chocobozzz
11 months ago
committed by
Chocobozzz
196 changed files with 5659 additions and 720 deletions
@ -0,0 +1,6 @@
|
||||
export const FileStorage = { |
||||
FILE_SYSTEM: 0, |
||||
OBJECT_STORAGE: 1 |
||||
} as const |
||||
|
||||
export type FileStorageType = typeof FileStorage[keyof typeof FileStorage] |
@ -1 +1,2 @@
|
||||
export * from './file-storage.enum.js' |
||||
export * from './result-list.model.js' |
||||
|
@ -0,0 +1,9 @@
|
||||
export * from './peertube-export-format/index.js' |
||||
export * from './user-export-request-result.model.js' |
||||
export * from './user-export-request.model.js' |
||||
export * from './user-export-state.enum.js' |
||||
export * from './user-export.model.js' |
||||
export * from './user-import.model.js' |
||||
export * from './user-import-state.enum.js' |
||||
export * from './user-import-result.model.js' |
||||
export * from './user-import-upload-result.model.js' |
@ -0,0 +1,18 @@
|
||||
import { UserActorImageJSON } from './actor-export.model.js' |
||||
|
||||
export interface AccountExportJSON { |
||||
url: string |
||||
|
||||
name: string |
||||
displayName: string |
||||
description: string |
||||
|
||||
updatedAt: string |
||||
createdAt: string |
||||
|
||||
avatars: UserActorImageJSON[] |
||||
|
||||
archiveFiles: { |
||||
avatar: string | null |
||||
} |
||||
} |
@ -0,0 +1,6 @@
|
||||
export interface UserActorImageJSON { |
||||
width: number |
||||
url: string |
||||
createdAt: string |
||||
updatedAt: string |
||||
} |
@ -0,0 +1,9 @@
|
||||
export interface BlocklistExportJSON { |
||||
instances: { |
||||
host: string |
||||
}[] |
||||
|
||||
actors: { |
||||
handle: string |
||||
}[] |
||||
} |
@ -0,0 +1,23 @@
|
||||
import { UserActorImageJSON } from './actor-export.model.js' |
||||
|
||||
export interface ChannelExportJSON { |
||||
channels: { |
||||
url: string |
||||
|
||||
name: string |
||||
displayName: string |
||||
description: string |
||||
support: string |
||||
|
||||
updatedAt: string |
||||
createdAt: string |
||||
|
||||
avatars: UserActorImageJSON[] |
||||
banners: UserActorImageJSON[] |
||||
|
||||
archiveFiles: { |
||||
avatar: string | null |
||||
banner: string | null |
||||
} |
||||
}[] |
||||
} |
@ -0,0 +1,12 @@
|
||||
export interface CommentsExportJSON { |
||||
comments: { |
||||
url: string |
||||
text: string |
||||
createdAt: string |
||||
videoUrl: string |
||||
|
||||
inReplyToCommentUrl?: string |
||||
|
||||
archiveFiles?: never |
||||
}[] |
||||
} |
@ -0,0 +1,8 @@
|
||||
export interface DislikesExportJSON { |
||||
dislikes: { |
||||
videoUrl: string |
||||
createdAt: string |
||||
|
||||
archiveFiles?: never |
||||
}[] |
||||
} |
@ -0,0 +1,9 @@
|
||||
export interface FollowersExportJSON { |
||||
followers: { |
||||
handle: string |
||||
createdAt: string |
||||
targetHandle: string |
||||
|
||||
archiveFiles?: never |
||||
}[] |
||||
} |
@ -0,0 +1,9 @@
|
||||
export interface FollowingExportJSON { |
||||
following: { |
||||
handle: string |
||||
targetHandle: string |
||||
createdAt: string |
||||
|
||||
archiveFiles?: never |
||||
}[] |
||||
} |
@ -0,0 +1,12 @@
|
||||
export * from './account-export.model.js' |
||||
export * from './actor-export.model.js' |
||||
export * from './blocklist-export.model.js' |
||||
export * from './channel-export.model.js' |
||||
export * from './comments-export.model.js' |
||||
export * from './dislikes-export.model.js' |
||||
export * from './followers-export.model.js' |
||||
export * from './following-export.model.js' |
||||
export * from './likes-export.model.js' |
||||
export * from './user-settings-export.model.js' |
||||
export * from './video-export.model.js' |
||||
export * from './video-playlists-export.model.js' |
@ -0,0 +1,8 @@
|
||||
export interface LikesExportJSON { |
||||
likes: { |
||||
videoUrl: string |
||||
createdAt: string |
||||
|
||||
archiveFiles?: never |
||||
}[] |
||||
} |
@ -0,0 +1,26 @@
|
||||
import { UserNotificationSetting } from '../../users/user-notification-setting.model.js' |
||||
import { NSFWPolicyType } from '../../videos/nsfw-policy.type.js' |
||||
|
||||
export interface UserSettingsExportJSON { |
||||
email: string |
||||
|
||||
emailPublic: boolean |
||||
nsfwPolicy: NSFWPolicyType |
||||
|
||||
autoPlayVideo: boolean |
||||
autoPlayNextVideo: boolean |
||||
autoPlayNextVideoPlaylist: boolean |
||||
|
||||
p2pEnabled: boolean |
||||
|
||||
videosHistoryEnabled: boolean |
||||
videoLanguages: string[] |
||||
|
||||
theme: string |
||||
|
||||
createdAt: Date |
||||
|
||||
notificationSettings: UserNotificationSetting |
||||
|
||||
archiveFiles?: never |
||||
} |
@ -0,0 +1,103 @@
|
||||
import { |
||||
LiveVideoLatencyModeType, |
||||
VideoPrivacyType, |
||||
VideoStateType, |
||||
VideoStreamingPlaylistType_Type |
||||
} from '../../videos/index.js' |
||||
|
||||
export interface VideoExportJSON { |
||||
videos: { |
||||
uuid: string |
||||
|
||||
createdAt: string |
||||
updatedAt: string |
||||
publishedAt: string |
||||
originallyPublishedAt: string |
||||
|
||||
name: string |
||||
category: number |
||||
licence: number |
||||
language: string |
||||
tags: string[] |
||||
|
||||
privacy: VideoPrivacyType |
||||
passwords: string[] |
||||
|
||||
duration: number |
||||
|
||||
description: string |
||||
support: string |
||||
|
||||
isLive: boolean |
||||
live?: { |
||||
saveReplay: boolean |
||||
permanentLive: boolean |
||||
latencyMode: LiveVideoLatencyModeType |
||||
streamKey: string |
||||
|
||||
replaySettings?: { |
||||
privacy: VideoPrivacyType |
||||
} |
||||
} |
||||
|
||||
url: string |
||||
|
||||
thumbnailUrl: string |
||||
previewUrl: string |
||||
|
||||
views: number |
||||
|
||||
likes: number |
||||
dislikes: number |
||||
|
||||
nsfw: boolean |
||||
|
||||
commentsEnabled: boolean |
||||
downloadEnabled: boolean |
||||
|
||||
channel: { |
||||
name: string |
||||
} |
||||
|
||||
waitTranscoding: boolean |
||||
state: VideoStateType |
||||
|
||||
captions: { |
||||
createdAt: string |
||||
updatedAt: string |
||||
language: string |
||||
filename: string |
||||
fileUrl: string |
||||
}[] |
||||
|
||||
files: VideoFileExportJSON[] |
||||
|
||||
streamingPlaylists: { |
||||
type: VideoStreamingPlaylistType_Type |
||||
playlistUrl: string |
||||
segmentsSha256Url: string |
||||
files: VideoFileExportJSON[] |
||||
}[] |
||||
|
||||
source?: { |
||||
filename: string |
||||
} |
||||
|
||||
archiveFiles: { |
||||
videoFile: string | null |
||||
thumbnail: string | null |
||||
captions: Record<string, string> // The key is the language code
|
||||
} |
||||
}[] |
||||
} |
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface VideoFileExportJSON { |
||||
resolution: number |
||||
size: number // Bytes
|
||||
fps: number |
||||
|
||||
torrentUrl: string |
||||
fileUrl: string |
||||
} |
@ -0,0 +1,34 @@
|
||||
import { VideoPlaylistPrivacyType } from '../../videos/playlist/video-playlist-privacy.model.js' |
||||
import { VideoPlaylistType_Type } from '../../videos/playlist/video-playlist-type.model.js' |
||||
|
||||
export interface VideoPlaylistsExportJSON { |
||||
videoPlaylists: { |
||||
displayName: string |
||||
description: string |
||||
privacy: VideoPlaylistPrivacyType |
||||
url: string |
||||
uuid: string |
||||
|
||||
type: VideoPlaylistType_Type |
||||
|
||||
channel: { |
||||
name: string |
||||
} |
||||
|
||||
createdAt: string |
||||
updatedAt: string |
||||
|
||||
thumbnailUrl: string |
||||
|
||||
elements: { |
||||
videoUrl: string |
||||
|
||||
startTimestamp?: number |
||||
stopTimestamp?: number |
||||
}[] |
||||
|
||||
archiveFiles: { |
||||
thumbnail: string | null |
||||
} |
||||
}[] |
||||
} |
@ -0,0 +1,5 @@
|
||||
export interface UserExportRequestResult { |
||||
export: { |
||||
id: number |
||||
} |
||||
} |
@ -0,0 +1,3 @@
|
||||
export interface UserExportRequest { |
||||
withVideoFiles: boolean |
||||
} |
@ -0,0 +1,8 @@
|
||||
export const UserExportState = { |
||||
PENDING: 1, |
||||
PROCESSING: 2, |
||||
COMPLETED: 3, |
||||
ERRORED: 4 |
||||
} as const |
||||
|
||||
export type UserExportStateType = typeof UserExportState[keyof typeof UserExportState] |
@ -0,0 +1,18 @@
|
||||
import { UserExportStateType } from './user-export-state.enum.js' |
||||
|
||||
export interface UserExport { |
||||
id: number |
||||
|
||||
state: { |
||||
id: UserExportStateType |
||||
label: string |
||||
} |
||||
|
||||
// In bytes
|
||||
size: number |
||||
|
||||
privateDownloadUrl: string |
||||
|
||||
createdAt: string | Date |
||||
expiresOn: string | Date |
||||
} |
@ -0,0 +1,20 @@
|
||||
type Summary = { |
||||
success: number |
||||
duplicates: number |
||||
errors: number |
||||
} |
||||
|
||||
export interface UserImportResultSummary { |
||||
stats: { |
||||
blocklist: Summary |
||||
channels: Summary |
||||
likes: Summary |
||||
dislikes: Summary |
||||
following: Summary |
||||
videoPlaylists: Summary |
||||
videos: Summary |
||||
|
||||
account: Summary |
||||
userSettings: Summary |
||||
} |
||||
} |
@ -0,0 +1,8 @@
|
||||
export const UserImportState = { |
||||
PENDING: 1, |
||||
PROCESSING: 2, |
||||
COMPLETED: 3, |
||||
ERRORED: 4 |
||||
} as const |
||||
|
||||
export type UserImportStateType = typeof UserImportState[keyof typeof UserImportState] |
@ -0,0 +1,5 @@
|
||||
export interface UserImportUploadResult { |
||||
userImport: { |
||||
id: number |
||||
} |
||||
} |
@ -0,0 +1,10 @@
|
||||
import { UserImportStateType } from './user-import-state.enum.js' |
||||
|
||||
export interface UserImport { |
||||
id: number |
||||
state: { |
||||
id: UserImportStateType |
||||
label: string |
||||
} |
||||
createdAt: string |
||||
} |
@ -1,6 +0,0 @@
|
||||
export const VideoStorage = { |
||||
FILE_SYSTEM: 0, |
||||
OBJECT_STORAGE: 1 |
||||
} as const |
||||
|
||||
export type VideoStorageType = typeof VideoStorage[keyof typeof VideoStorage] |
@ -0,0 +1,100 @@
|
||||
import express from 'express' |
||||
import { FileStorage, HttpStatusCode, UserExportRequest, UserExportRequestResult, UserExportState } from '@peertube/peertube-models' |
||||
import { |
||||
apiRateLimiter, |
||||
asyncMiddleware, |
||||
authenticate, |
||||
userExportDeleteValidator, |
||||
userExportRequestValidator, |
||||
userExportsListValidator |
||||
} from '../../../middlewares/index.js' |
||||
import { UserExportModel } from '@server/models/user/user-export.js' |
||||
import { getFormattedObjects } from '@server/helpers/utils.js' |
||||
import { sequelizeTypescript } from '@server/initializers/database.js' |
||||
import { JobQueue } from '@server/lib/job-queue/job-queue.js' |
||||
import { CONFIG } from '@server/initializers/config.js' |
||||
|
||||
const userExportsRouter = express.Router() |
||||
|
||||
userExportsRouter.use(apiRateLimiter) |
||||
|
||||
userExportsRouter.post('/:userId/exports/request', |
||||
authenticate, |
||||
asyncMiddleware(userExportRequestValidator), |
||||
asyncMiddleware(requestExport) |
||||
) |
||||
|
||||
userExportsRouter.get('/:userId/exports', |
||||
authenticate, |
||||
asyncMiddleware(userExportsListValidator), |
||||
asyncMiddleware(listUserExports) |
||||
) |
||||
|
||||
userExportsRouter.delete('/:userId/exports/:id', |
||||
authenticate, |
||||
asyncMiddleware(userExportDeleteValidator), |
||||
asyncMiddleware(deleteUserExport) |
||||
) |
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export { |
||||
userExportsRouter |
||||
} |
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function requestExport (req: express.Request, res: express.Response) { |
||||
const body = req.body as UserExportRequest |
||||
|
||||
const exportModel = new UserExportModel({ |
||||
state: UserExportState.PENDING, |
||||
withVideoFiles: body.withVideoFiles, |
||||
|
||||
storage: CONFIG.OBJECT_STORAGE.ENABLED |
||||
? FileStorage.OBJECT_STORAGE |
||||
: FileStorage.FILE_SYSTEM, |
||||
|
||||
userId: res.locals.user.id, |
||||
createdAt: new Date() |
||||
}) |
||||
exportModel.generateAndSetFilename() |
||||
|
||||
await sequelizeTypescript.transaction(async transaction => { |
||||
await exportModel.save({ transaction }) |
||||
}) |
||||
|
||||
await JobQueue.Instance.createJob({ type: 'create-user-export', payload: { userExportId: exportModel.id } }) |
||||
|
||||
return res.json({ |
||||
export: { |
||||
id: exportModel.id |
||||
} |
||||
} as UserExportRequestResult) |
||||
} |
||||
|
||||
async function listUserExports (req: express.Request, res: express.Response) { |
||||
const resultList = await UserExportModel.listForApi({ |
||||
start: req.query.start, |
||||
count: req.query.count, |
||||
user: res.locals.user |
||||
}) |
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total)) |
||||
} |
||||
|
||||
async function deleteUserExport (req: express.Request, res: express.Response) { |
||||
const userExport = res.locals.userExport |
||||
|
||||
await sequelizeTypescript.transaction(async transaction => { |
||||
await userExport.reload({ transaction }) |
||||
|
||||
if (!userExport.canBeSafelyRemoved()) { |
||||
return res.sendStatus(HttpStatusCode.CONFLICT_409) |
||||
} |
||||
|
||||
await userExport.destroy({ transaction }) |
||||
}) |
||||
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204) |
||||
} |
@ -0,0 +1,90 @@
|
||||
import express from 'express' |
||||
import { |
||||
apiRateLimiter, |
||||
asyncMiddleware, |
||||
authenticate |
||||
} from '../../../middlewares/index.js' |
||||
import { uploadx } from '@server/lib/uploadx.js' |
||||
import { |
||||
getLatestImportStatusValidator, |
||||
userImportRequestResumableInitValidator, |
||||
userImportRequestResumableValidator |
||||
} from '@server/middlewares/validators/users/user-import.js' |
||||
import { HttpStatusCode, UserImportState, UserImportUploadResult } from '@peertube/peertube-models' |
||||
import { logger } from '@server/helpers/logger.js' |
||||
import { UserImportModel } from '@server/models/user/user-import.js' |
||||
import { getFSUserImportFilePath } from '@server/lib/paths.js' |
||||
import { move } from 'fs-extra/esm' |
||||
import { JobQueue } from '@server/lib/job-queue/job-queue.js' |
||||
import { saveInTransactionWithRetries } from '@server/helpers/database-utils.js' |
||||
|
||||
const userImportRouter = express.Router() |
||||
|
||||
userImportRouter.use(apiRateLimiter) |
||||
|
||||
userImportRouter.post('/:userId/imports/import-resumable', |
||||
authenticate, |
||||
asyncMiddleware(userImportRequestResumableInitValidator), |
||||
(req, res) => uploadx.upload(req, res) // Prevent next() call, explicitely tell to uploadx it's the end
|
||||
) |
||||
|
||||
userImportRouter.delete('/:userId/imports/import-resumable', |
||||
authenticate, |
||||
(req, res) => uploadx.upload(req, res) // Prevent next() call, explicitely tell to uploadx it's the end
|
||||
) |
||||
|
||||
userImportRouter.put('/:userId/imports/import-resumable', |
||||
authenticate, |
||||
uploadx.upload, // uploadx doesn't next() before the file upload completes
|
||||
asyncMiddleware(userImportRequestResumableValidator), |
||||
asyncMiddleware(addUserImportResumable) |
||||
) |
||||
|
||||
userImportRouter.get('/:userId/imports/latest', |
||||
authenticate, |
||||
asyncMiddleware(getLatestImportStatusValidator), |
||||
asyncMiddleware(getLatestImport) |
||||
) |
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export { |
||||
userImportRouter |
||||
} |
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function addUserImportResumable (req: express.Request, res: express.Response) { |
||||
const file = res.locals.importUserFileResumable |
||||
const user = res.locals.user |
||||
|
||||
// Move import
|
||||
const userImport = new UserImportModel({ |
||||
state: UserImportState.PENDING, |
||||
userId: user.id, |
||||
createdAt: new Date() |
||||
}) |
||||
userImport.generateAndSetFilename() |
||||
|
||||
await move(file.path, getFSUserImportFilePath(userImport)) |
||||
|
||||
await saveInTransactionWithRetries(userImport) |
||||
|
||||
// Create job
|
||||
await JobQueue.Instance.createJob({ type: 'import-user-archive', payload: { userImportId: userImport.id } }) |
||||
|
||||
logger.info('User import request job created for user ' + user.username) |
||||
|
||||
return res.json({ |
||||
userImport: { |
||||
id: userImport.id |
||||
} |
||||
} as UserImportUploadResult) |
||||
} |
||||
|
||||
async function getLatestImport (req: express.Request, res: express.Response) { |
||||
const userImport = await UserImportModel.loadLatestByUserId(res.locals.user.id) |
||||
if (!userImport) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) |
||||
|
||||
return res.json(userImport.toFormattedJSON()) |
||||
} |
@ -0,0 +1,55 @@
|
||||
import { createWriteStream } from 'fs' |
||||
import { ensureDir } from 'fs-extra/esm' |
||||
import { dirname, join } from 'path' |
||||
import { pipeline } from 'stream' |
||||
import * as yauzl from 'yauzl' |
||||
import { logger, loggerTagsFactory } from './logger.js' |
||||
|
||||
const lTags = loggerTagsFactory('unzip') |
||||
|
||||
export async function unzip (source: string, destination: string) { |
||||
await ensureDir(destination) |
||||
|
||||
logger.info(`Unzip ${source} to ${destination}`, lTags()) |
||||
|
||||
return new Promise<void>((res, rej) => { |
||||
yauzl.open(source, { lazyEntries: true }, (err, zipFile) => { |
||||
if (err) return rej(err) |
||||
|
||||
zipFile.readEntry() |
||||
|
||||
zipFile.on('entry', async entry => { |
||||
const entryPath = join(destination, entry.fileName) |
||||
|
||||
try { |
||||
if (/\/$/.test(entry.fileName)) { |
||||
await ensureDir(entryPath) |
||||
logger.debug(`Creating directory from zip ${entryPath}`, lTags()) |
||||
|
||||
zipFile.readEntry() |
||||
return |
||||
} |
||||
|
||||
await ensureDir(dirname(entryPath)) |
||||
} catch (err) { |
||||
return rej(err) |
||||
} |
||||
|
||||
zipFile.openReadStream(entry, (readErr, readStream) => { |
||||
if (readErr) return rej(readErr) |
||||
|
||||
logger.debug(`Creating file from zip ${entryPath}`, lTags()) |
||||
|
||||
const writeStream = createWriteStream(entryPath) |
||||
writeStream.on('close', () => zipFile.readEntry()) |
||||
|
||||
pipeline(readStream, writeStream, pipelineErr => { |
||||
if (pipelineErr) return rej(pipelineErr) |
||||
}) |
||||
}) |
||||
}) |
||||
|
||||
zipFile.on('end', () => res()) |
||||
}) |
||||
}) |
||||
} |
@ -0,0 +1,33 @@
|
||||
import * as Sequelize from 'sequelize' |
||||
|
||||
async function up (utils: { |
||||
transaction: Sequelize.Transaction |
||||
queryInterface: Sequelize.QueryInterface |
||||
sequelize: Sequelize.Sequelize |
||||
}): Promise<void> { |
||||
const query = ` |
||||
CREATE TABLE IF NOT EXISTS "userExport" ( |
||||
"id" SERIAL, |
||||
"filename" VARCHAR(255), |
||||
"withVideoFiles" BOOLEAN NOT NULL, |
||||
"state" INTEGER NOT NULL, |
||||
"error" TEXT, |
||||
"size" INTEGER, |
||||
"storage" INTEGER NOT NULL, |
||||
"userId" INTEGER NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, |
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, |
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, |
||||
PRIMARY KEY ("id") |
||||
);` |
||||
|
||||
await utils.sequelize.query(query, { transaction: utils.transaction }) |
||||
} |
||||
|
||||
function down (options) { |
||||
throw new Error('Not implemented.') |
||||
} |
||||
|
||||
export { |
||||
up, |
||||
down |
||||
} |
@ -0,0 +1,31 @@
|
||||
import * as Sequelize from 'sequelize' |
||||
|
||||
async function up (utils: { |
||||
transaction: Sequelize.Transaction |
||||
queryInterface: Sequelize.QueryInterface |
||||
sequelize: Sequelize.Sequelize |
||||
}): Promise<void> { |
||||
const query = ` |
||||
CREATE TABLE IF NOT EXISTS "userImport" ( |
||||
"id" SERIAL, |
||||
"filename" VARCHAR(255), |
||||
"state" INTEGER NOT NULL, |
||||
"error" TEXT, |
||||
"resultSummary" JSONB, |
||||
"userId" INTEGER NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE, |
||||
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, |
||||
"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, |
||||
PRIMARY KEY ("id") |
||||
);;` |
||||
|
||||
await utils.sequelize.query(query, { transaction: utils.transaction }) |
||||
} |
||||
|
||||
function down (options) { |
||||
throw new Error('Not implemented.') |
||||
} |
||||
|
||||
export { |
||||
up, |
||||
down |
||||
} |
@ -0,0 +1,9 @@
|
||||
extends ../common/greetings |
||||
include ../common/mixins.pug |
||||
|
||||
block title |
||||
| Your export archive has been created |
||||
|
||||
block content |
||||
p |
||||
| Your export archive has been created. You can download it in #[a(href=exportsUrl) your account export page]. |
@ -0,0 +1,12 @@
|
||||
extends ../common/greetings |
||||
include ../common/mixins.pug |
||||
|
||||
block title |
||||
| Failed to create your export archive |
||||
|
||||
block content |
||||
p |
||||
| We are sorry but the generation of your export archive has failed: |
||||
blockquote !{errorMessage} |
||||
p |
||||
| Please contact your administrator if the problem occurs again. |
@ -0,0 +1,46 @@
|
||||
extends ../common/greetings |
||||
include ../common/mixins.pug |
||||
|
||||
mixin displaySummary(stats) |
||||
ul |
||||
if stats.success |
||||
li Imported: #{stats.success} |
||||
if stats.duplicates |
||||
li Not imported as considered duplicate: #{stats.duplicates} |
||||
if stats.errors |
||||
li Not imported due to error: #{stats.errors} |
||||
|
||||
block title |
||||
| Your archive import has finished |
||||
|
||||
block content |
||||
p Your archive import has finished. Here is the summary of imported objects: |
||||
|
||||
ul |
||||
li |
||||
strong User settings: |
||||
+displaySummary(resultStats.userSettings) |
||||
li |
||||
strong Account (name, description, avatar...): |
||||
+displaySummary(resultStats.account) |
||||
li |
||||
strong Blocklist: |
||||
+displaySummary(resultStats.blocklist) |
||||
li |
||||
strong Channels: |
||||
+displaySummary(resultStats.channels) |
||||
li |
||||
strong Likes: |
||||
+displaySummary(resultStats.likes) |
||||
li |
||||
strong Dislikes: |
||||
+displaySummary(resultStats.dislikes) |
||||
li |
||||
strong Subscriptions: |
||||
+displaySummary(resultStats.following) |
||||
li |
||||
strong Video Playlists: |
||||
+displaySummary(resultStats.videoPlaylists) |
||||
li |
||||
strong Videos: |
||||
+displaySummary(resultStats.videos) |
@ -0,0 +1,12 @@
|
||||
extends ../common/greetings |
||||
include ../common/mixins.pug |
||||
|
||||
block title |
||||
| Failed to import your archive |
||||
|
||||
block content |
||||
p |
||||
| We are sorry but the import of your archive has failed: |
||||
blockquote !{errorMessage} |
||||
p |
||||
| Please contact your administrator if the problem occurs again. |
@ -0,0 +1,34 @@
|
||||
import { Job } from 'bullmq' |
||||
import { logger, loggerTagsFactory } from '@server/helpers/logger.js' |
||||
import { CreateUserExportPayload } from '@peertube/peertube-models' |
||||
import { UserExportModel } from '@server/models/user/user-export.js' |
||||
import { UserExporter } from '@server/lib/user-import-export/user-exporter.js' |
||||
import { Emailer } from '@server/lib/emailer.js' |
||||
|
||||
const lTags = loggerTagsFactory('user-export') |
||||
|
||||
export async function processCreateUserExport (job: Job): Promise<void> { |
||||
const payload = job.data as CreateUserExportPayload |
||||
const exportModel = await UserExportModel.load(payload.userExportId) |
||||
|
||||
logger.info('Processing create user export %s in job %s.', payload.userExportId, job.id, lTags()) |
||||
|
||||
if (!exportModel) { |
||||
logger.info(`User export ${payload.userExportId} does not exist anymore, do not create user export.`, lTags()) |
||||
return |
||||
} |
||||
|
||||
const exporter = new UserExporter() |
||||
|
||||
try { |
||||
await exporter.export(exportModel) |
||||
|
||||
await Emailer.Instance.addUserExportCompletedOrErroredJob(exportModel) |
||||
|
||||
logger.info(`User export ${payload.userExportId} has been created`, lTags()) |
||||
} catch (err) { |
||||
await Emailer.Instance.addUserExportCompletedOrErroredJob(exportModel) |
||||
|
||||
throw err |
||||
} |
||||
} |
@ -0,0 +1,33 @@
|
||||
import { Job } from 'bullmq' |
||||
import { logger, loggerTagsFactory } from '@server/helpers/logger.js' |
||||
import { ImportUserArchivePayload } from '@peertube/peertube-models' |
||||
import { UserImportModel } from '@server/models/user/user-import.js' |
||||
import { UserImporter } from '@server/lib/user-import-export/user-importer.js' |
||||
import { Emailer } from '@server/lib/emailer.js' |
||||
|
||||
const lTags = loggerTagsFactory('user-import') |
||||
|
||||
export async function processImportUserArchive (job: Job): Promise<void> { |
||||
const payload = job.data as ImportUserArchivePayload |
||||
const importModel = await UserImportModel.load(payload.userImportId) |
||||
|
||||
logger.info(`Processing importing user archive ${payload.userImportId} in job ${job.id}`, lTags()) |
||||
|
||||
if (!importModel) { |
||||
logger.info(`User import ${payload.userImportId} does not exist anymore, do not create import data.`, lTags()) |
||||
return |
||||
} |
||||
|
||||
const exporter = new UserImporter() |
||||
await exporter.import(importModel) |
||||
|
||||
try { |
||||
await Emailer.Instance.addUserImportSuccessJob(importModel) |
||||
|
||||
logger.info(`User import ${payload.userImportId} ended`, lTags()) |
||||
} catch (err) { |
||||
await Emailer.Instance.addUserImportErroredJob(importModel) |
||||
|
||||
throw err |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue