Browse Source
- stores all data related to one user to a .zip file on storage - bans the user - removes the userdevelop
21 changed files with 962 additions and 16 deletions
@ -0,0 +1,29 @@ |
|||||
|
// user-archive.js
|
||||
|
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
|
// License: Apache-2.0
|
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
const mongoose = require('mongoose'); |
||||
|
|
||||
|
const Schema = mongoose.Schema; |
||||
|
|
||||
|
const UserArchiveSchema = new Schema({ |
||||
|
created: { type: Date, default: Date.now, required: true, index: -1 }, |
||||
|
user: { |
||||
|
_id: { type: Schema.ObjectId, required: true, index: 1 }, |
||||
|
email: { type: String }, |
||||
|
username: { type: String, required: true }, |
||||
|
}, |
||||
|
archive: { |
||||
|
bucket: { type: String, required: true }, |
||||
|
key: { type: String, required: true }, |
||||
|
etag: { type: String, required: true }, |
||||
|
size: { type: Number, required: true }, |
||||
|
}, |
||||
|
notes: { type: String }, |
||||
|
}); |
||||
|
|
||||
|
module.exports = (conn) => { |
||||
|
return conn.model('UserArchive', UserArchiveSchema); |
||||
|
}; |
@ -0,0 +1,28 @@ |
|||||
|
extends ../layouts/main |
||||
|
block content |
||||
|
|
||||
|
include ../user/components/list-item |
||||
|
|
||||
|
form(method="POST", action=`/admin/user/local/${image.owner._id}/archive`).uk-form |
||||
|
input(type="hidden", name="userId", value= image.owner._id) |
||||
|
.uk-card.uk-card-default.uk-card-small |
||||
|
.uk-card-header |
||||
|
h1.uk-card-title Archive Local User |
||||
|
.uk-card-body |
||||
|
p This action will pull all images from storage into an archive file, place the archive file on storage, delete all the image records and storage data, then ban the User. The archive is produced first because images would be deleted during the ban. So, the archive is made, then the user is banned. |
||||
|
|
||||
|
p These are the #{numeral(imageHistory.length).format('0,0')} most recent images uploaded by #{image.owner.username}. |
||||
|
|
||||
|
div(uk-grid) |
||||
|
each image in imageHistory |
||||
|
.uk-width-medium |
||||
|
.uk-margin-small(uk-lightbox) |
||||
|
a(href=`/image/${image._id}`, data-type="image", data-caption=`id: ${image._id}`) |
||||
|
div |
||||
|
img(src= `/image/${image._id}`).responsive |
||||
|
.uk-card-footer.uk-flex.uk-flex-middle |
||||
|
.uk-width-expand |
||||
|
+renderBackButton() |
||||
|
|
||||
|
.uk-width-auto |
||||
|
button(type="submit").uk-button.uk-button-danger.uk-border-rounded Archive User |
@ -0,0 +1,28 @@ |
|||||
|
extends ../../layouts/main |
||||
|
block content |
||||
|
|
||||
|
include ../../user/components/list-item |
||||
|
|
||||
|
form(method="POST", action=`/admin/user/local/${userAccount._id}/archive`).uk-form |
||||
|
input(type="hidden", name="userId", value= userAccount._id) |
||||
|
.uk-card.uk-card-default.uk-card-small |
||||
|
.uk-card-header |
||||
|
h1.uk-card-title |
||||
|
span |
||||
|
i.fas.fa-id-card |
||||
|
span.uk-margin-small-left Archive Local User |
||||
|
.uk-card-body |
||||
|
.uk-margin |
||||
|
+renderUserListItem(userAccount) |
||||
|
|
||||
|
.uk-margin |
||||
|
p This action will archive #{userAccount.displayName || userAccount.username}'s content to a .zip file, place the .zip file on storage, create a UserArchive record for this User account, ban this User account, and remove this User account from the database. |
||||
|
|
||||
|
p #{userAccount.displayName || userAccount.username}'s email address and username will become locked, and will remain unavailable for use for as long as this archive exists. |
||||
|
|
||||
|
.uk-card-footer.uk-flex.uk-flex-middle |
||||
|
.uk-width-expand |
||||
|
+renderBackButton() |
||||
|
|
||||
|
.uk-width-auto |
||||
|
button(type="submit").uk-button.uk-button-danger.uk-border-rounded Archive User |
@ -0,0 +1,41 @@ |
|||||
|
extends ../../layouts/main |
||||
|
block content |
||||
|
|
||||
|
include ../components/list-item |
||||
|
include ../../../components/pagination-bar |
||||
|
|
||||
|
.uk-card.uk-card-default.uk-card-small.uk-margin |
||||
|
.uk-card-header |
||||
|
h1.uk-card-title |
||||
|
span |
||||
|
i.fas.fa-id-card |
||||
|
span.uk-margin-small-left User Archives |
||||
|
|
||||
|
.uk-card-body |
||||
|
if Array.isArray(archive.archives) && (archive.archives.length > 0) |
||||
|
table.uk-table.uk-table-divider.uk-table-justify |
||||
|
thead |
||||
|
tr |
||||
|
th Username |
||||
|
th User ID |
||||
|
th Created |
||||
|
th Archive |
||||
|
tbody |
||||
|
each record in archive.archives |
||||
|
tr |
||||
|
td= record.user.username |
||||
|
td= record.user._id |
||||
|
td= moment(record.created).format('MMMM DD, YYYY, [at] h:mm a') |
||||
|
td |
||||
|
span |
||||
|
i.fas.fa-file-archive |
||||
|
a(href=`/admin/user/archive/${record._id}`).uk-margin-small-left View Archive |
||||
|
else |
||||
|
div There are no user archives. |
||||
|
|
||||
|
if Array.isArray(archive.archives) && (archive.archives.length > 0) |
||||
|
.uk-card-footer |
||||
|
+renderPaginationBar('/admin/user/archive', archive.totalArchiveCount) |
||||
|
|
||||
|
.uk-margin |
||||
|
.uk-text-small.uk-text-muted.uk-text-center User accounts referenced on this page have been removed from the database and are no longer able to use #{site.name}. |
@ -0,0 +1,7 @@ |
|||||
|
extends ../../layouts/main |
||||
|
block content |
||||
|
|
||||
|
include ../components/list-item |
||||
|
|
||||
|
h1 User Archive Job |
||||
|
pre= JSON.stringify(job, null, 2) |
@ -0,0 +1,83 @@ |
|||||
|
extends ../../layouts/main |
||||
|
block content |
||||
|
|
||||
|
include ../components/list-item |
||||
|
|
||||
|
form(method="POST", action=`/admin/user/archive/${archive._id}/action`).uk-form |
||||
|
.uk-card.uk-card-default.uk-card-small |
||||
|
.uk-card-header |
||||
|
h1.uk-card-title |
||||
|
span |
||||
|
i.fas.fa-id-card |
||||
|
span.uk-margin-small-left User Archive |
||||
|
|
||||
|
.uk-card-body |
||||
|
.uk-margin |
||||
|
div(uk-grid) |
||||
|
.uk-width-auto |
||||
|
.uk-form-label Archive ID |
||||
|
.uk-text-bold= archive._id |
||||
|
.uk-width-auto |
||||
|
.uk-form-label Created |
||||
|
.uk-text-bold= moment(archive.created).format('MMMM DD, YYYY, [at] h:mm:ss a') |
||||
|
.uk-width-auto |
||||
|
.uk-form-label User |
||||
|
.uk-text-bold= archive.user.username |
||||
|
.uk-width-auto |
||||
|
.uk-form-label User ID |
||||
|
.uk-text-bold= archive.user._id |
||||
|
.uk-width-auto |
||||
|
.uk-form-label User email |
||||
|
.uk-text-bold= archive.user.email |
||||
|
|
||||
|
if archive.archive |
||||
|
div(uk-grid) |
||||
|
.uk-width-auto |
||||
|
.uk-form-label Archive file |
||||
|
.uk-text-bold= archive.archive.key.replace(/\/user-archive\//, '') |
||||
|
.uk-width-auto |
||||
|
.uk-form-label Download size |
||||
|
.uk-text-bold= numeral(archive.archive.size).format('0,0.0a') |
||||
|
else |
||||
|
.uk-text-italic (archive file removed) |
||||
|
|
||||
|
.uk-margin |
||||
|
label(for="notes").uk-form-label Notes |
||||
|
textarea(id="notes", name="notes", rows="4", placeholder="Enter notes").uk-textarea.uk-resize-vertical= archive.notes |
||||
|
|
||||
|
.uk-card-footer |
||||
|
div(uk-grid) |
||||
|
.uk-width-expand |
||||
|
div(hidden= !archive.archive, uk-grid) |
||||
|
.uk-width-auto |
||||
|
a(href=`/admin/user/archive/${archive._id}/file`).uk-button.uk-button-default.uk-border-rounded |
||||
|
span |
||||
|
i.fas.fa-download |
||||
|
span.uk-margin-small-left Download#[span(class="uk-visible@s") File] |
||||
|
.uk-width-auto |
||||
|
button( |
||||
|
type="submit", |
||||
|
name="action", |
||||
|
value="delete-file", |
||||
|
uk-tooltip={ title: 'Remove the .zip file attached to the UserArchive' }, |
||||
|
).uk-button.uk-button-danger.uk-border-rounded |
||||
|
span |
||||
|
i.fas.fa-trash |
||||
|
span.uk-margin-small-left Delete#[span(class="uk-visible@s") File] |
||||
|
|
||||
|
.uk-width-auto |
||||
|
button( |
||||
|
type="submit", |
||||
|
name="action", |
||||
|
value="delete", |
||||
|
uk-tooltip={ title: 'Remove the UserArchive from the database' }, |
||||
|
).uk-button.uk-button-danger.uk-border-rounded |
||||
|
span |
||||
|
i.fas.fa-save |
||||
|
span.uk-margin-small-left Delete |
||||
|
|
||||
|
.uk-width-auto |
||||
|
button(type="submit", name="action", value="update").uk-button.uk-button-primary.uk-border-rounded |
||||
|
span |
||||
|
i.fas.fa-save |
||||
|
span.uk-margin-small-left Update |
@ -0,0 +1,381 @@ |
|||||
|
// reeeper/job/archive-user-local.js
|
||||
|
// Copyright (C) 2022 DTP Technologies, LLC
|
||||
|
// License: Apache-2.0
|
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
const path = require('path'); |
||||
|
const fs = require('fs'); |
||||
|
|
||||
|
const util = require('util'); |
||||
|
const execFile = util.promisify(require('child_process').execFile); |
||||
|
|
||||
|
const mime = require('mime'); |
||||
|
|
||||
|
const mongoose = require('mongoose'); |
||||
|
const User = mongoose.model('User'); |
||||
|
|
||||
|
const { SiteWorkerProcess } = require(path.join(__dirname, '..', '..', '..', '..', 'lib', 'site-lib')); |
||||
|
|
||||
|
/** |
||||
|
* A job to archive and ban a User (local). |
||||
|
* |
||||
|
* 1. Immediately disable the specified User |
||||
|
* 2. Create a .zip file of the User's content on storage |
||||
|
* 3. Creates a UserArchive record for the file and User |
||||
|
* 4. Ban the User (removes all of the User's content) |
||||
|
* 5. Remove the User record from the database |
||||
|
*/ |
||||
|
class ArchiveUserLocalJob extends SiteWorkerProcess { |
||||
|
|
||||
|
static get COMPONENT ( ) { |
||||
|
return { |
||||
|
name: 'archiveUserLocalJob', |
||||
|
slug: 'archive-user-local-job', |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
static get JOB_NAME ( ) { return 'Local User Archive'; } |
||||
|
static get JOB_SLUG ( ) { return 'archive-user-local'; } |
||||
|
|
||||
|
constructor (worker) { |
||||
|
super(worker, ArchiveUserLocalJob.COMPONENT); |
||||
|
this.jobs = new Set(); |
||||
|
} |
||||
|
|
||||
|
async start ( ) { |
||||
|
await super.start(); |
||||
|
|
||||
|
this.queue = await this.getJobQueue('reeeper', this.dtp.config.jobQueues.reeeper); |
||||
|
|
||||
|
this.log.info('registering job processor', { queue: this.queue.name, job: ArchiveUserLocalJob.JOB_SLUG }); |
||||
|
this.queue.process(ArchiveUserLocalJob.JOB_SLUG, 1, this.processArchiveUserLocal.bind(this)); |
||||
|
} |
||||
|
|
||||
|
async stop ( ) { |
||||
|
try { |
||||
|
if (this.queue) { |
||||
|
this.log.info('halting job queue', { jobCount: this.jobs.size }); |
||||
|
await this.queue.pause(true, false); |
||||
|
delete this.queue; |
||||
|
} |
||||
|
} catch (error) { |
||||
|
this.log.error('failed to halt job queue', { error }); |
||||
|
// fall through
|
||||
|
} finally { |
||||
|
await super.stop(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async processArchiveUserLocal (job) { |
||||
|
const { user: userService } = this.dtp.services; |
||||
|
try { |
||||
|
job.data.archivePath = path.join('/tmp', this.dtp.pkg.name, ArchiveUserLocalJob.JOB_SLUG); |
||||
|
this.jobs.add(job); |
||||
|
|
||||
|
job.data.userId = mongoose.Types.ObjectId(job.data.userId); |
||||
|
job.data.user = await userService.getLocalUserAccount(job.data.userId); |
||||
|
|
||||
|
job.data.workPath = path.join(job.data.archivePath, job.data.userId.toString()); |
||||
|
await fs.promises.mkdir(job.data.workPath, { recursive: true }); |
||||
|
|
||||
|
/* |
||||
|
* Save the User account data |
||||
|
*/ |
||||
|
await this.archiveUserData(job); |
||||
|
|
||||
|
/* |
||||
|
* Disable the User account (which destroys their session and cookie(s)) |
||||
|
*/ |
||||
|
// await this.disableUser(job);
|
||||
|
|
||||
|
/* |
||||
|
* Archive the User's content to the workPath on the local file system. |
||||
|
*/ |
||||
|
await this.archiveUserChat(job); |
||||
|
await this.archiveUserComments(job); |
||||
|
await this.archiveUserStickers(job); |
||||
|
await this.archiveUserImages(job); |
||||
|
|
||||
|
/* |
||||
|
* Create the .zip file archive, upload it to storage, and create the |
||||
|
* UserArchive record. |
||||
|
*/ |
||||
|
await this.createArchiveFile(job); |
||||
|
|
||||
|
this.log.info('banning user', { |
||||
|
user: { |
||||
|
_id: job.data.userId, |
||||
|
username: job.data.user.username, |
||||
|
}, |
||||
|
}); |
||||
|
// await userService.ban(job.data.user);
|
||||
|
|
||||
|
this.log.info('removing user', { |
||||
|
user: { |
||||
|
_id: job.data.userId, |
||||
|
username: job.data.user.username, |
||||
|
}, |
||||
|
}); |
||||
|
// await User.deleteOne({ _id: job.data.userId });
|
||||
|
} catch (error) { |
||||
|
this.log.error('failed to delete attachment', { attachmentId: job.data.attachmentId, error }); |
||||
|
throw error; |
||||
|
} finally { |
||||
|
if (job.data.workPath) { |
||||
|
this.log.info('cleaning up work directory'); |
||||
|
await fs.promises.rm(job.data.workPath, { force: true, recursive: true }); |
||||
|
|
||||
|
delete job.data.workPath; |
||||
|
} |
||||
|
this.jobs.delete(job); |
||||
|
this.log.info('job complete', { job: job.id, name: ArchiveUserLocalJob.JOB_NAME }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async archiveUserData (job) { |
||||
|
// fetch the entire User record (all fields)
|
||||
|
job.data.fullUser = await User |
||||
|
.findOne({ _id: job.data.user._id }) |
||||
|
.select('+email +passwordSalt +password +flags +permissions +optIn') |
||||
|
.lean(); |
||||
|
if (!job.data.fullUser) { |
||||
|
throw new Error('user does not exist'); |
||||
|
} |
||||
|
|
||||
|
const userFilename = path.join(job.data.workPath, `user-${job.data.user._id}.json`); |
||||
|
await fs.promises.writeFile(userFilename, JSON.stringify(job.data.fullUser, null, 2)); |
||||
|
} |
||||
|
|
||||
|
async disableUser (job) { |
||||
|
this.log.info('disabling local User account', { |
||||
|
user: { |
||||
|
_id: job.data.userId, |
||||
|
username: job.data.user.username, |
||||
|
}, |
||||
|
}); |
||||
|
await User.updateOne( |
||||
|
{ _id: job.data.user._id }, |
||||
|
{ |
||||
|
$set: { |
||||
|
'flags.isAdmin': false, |
||||
|
'flags.isModerator': false, |
||||
|
'flags.isEmailVerified': false, |
||||
|
'permissions.canLogin': false, |
||||
|
'permissions.canChat': false, |
||||
|
'permissions.canComment': false, |
||||
|
'permissions.canReport': false, |
||||
|
'optIn.system': false, |
||||
|
'optIn.marketing': false, |
||||
|
}, |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
async archiveUserChat (job) { |
||||
|
const ChatMessage = mongoose.model('ChatMessage'); |
||||
|
const ChatRoom = mongoose.model('ChatRoom'); |
||||
|
|
||||
|
job.data.chatPath = path.join(job.data.workPath, 'chat'); |
||||
|
await fs.promises.mkdir(job.data.chatPath, { recursive: true }); |
||||
|
|
||||
|
this.log.info('archiving user chat', { |
||||
|
user: { |
||||
|
_id: job.data.user._id, |
||||
|
username: job.data.user.username, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
await ChatRoom |
||||
|
.find({ owner: job.data.user._id }) |
||||
|
|
||||
|
.lean() |
||||
|
.cursor() |
||||
|
.eachAsync(async (room) => { |
||||
|
const roomFilename = path.join(job.data.workPath, 'chat', `room-${room._id}`); |
||||
|
await fs.promises.writeFile(roomFilename, JSON.stringify(room, null, 2)); |
||||
|
}); |
||||
|
|
||||
|
await ChatMessage |
||||
|
.find({ author: job.data.user._id }) |
||||
|
.lean() |
||||
|
.cursor() |
||||
|
.eachAsync(async (message) => { |
||||
|
const messageFilename = path.join(job.data.workPath, 'chat', `message-${message._id}.json`); |
||||
|
await fs.promises.writeFile(messageFilename, JSON.stringify(message, null, 2)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async archiveUserComments (job) { |
||||
|
const Comment = mongoose.model('Comment'); |
||||
|
|
||||
|
job.data.commentPath = path.join(job.data.workPath, 'comments'); |
||||
|
await fs.promises.mkdir(job.data.commentPath, { recursive: true }); |
||||
|
|
||||
|
this.log.info('archiving user comments', { |
||||
|
user: { |
||||
|
_id: job.data.user._id, |
||||
|
username: job.data.user.username, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
await Comment |
||||
|
.find({ author: job.data.userId }) |
||||
|
.cursor() |
||||
|
.eachAsync(async (comment) => { |
||||
|
const commentFilename = path.join(job.data.imagePath, `comment-${comment._id}.json`); |
||||
|
await fs.promises.writeFile(commentFilename, JSON.stringify(comment, null, 2)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async archiveUserStickers (job) { |
||||
|
const Sticker = mongoose.model('Sticker'); |
||||
|
const { minio: minioService } = this.dtp.services; |
||||
|
|
||||
|
job.data.stickerPath = path.join(job.data.workPath, 'stickers'); |
||||
|
await fs.promises.mkdir(job.data.stickerPath, { recursive: true }); |
||||
|
|
||||
|
job.data.stickerMediaPath = path.join(job.data.stickerPath, 'media'); |
||||
|
await fs.promises.mkdir(job.data.stickerMediaPath, { recursive: true }); |
||||
|
|
||||
|
this.log.info('archiving user stickers', { |
||||
|
user: { |
||||
|
_id: job.data.user._id, |
||||
|
username: job.data.user.username, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
await Sticker |
||||
|
.find({ owner: job.data.userId }) |
||||
|
.cursor() |
||||
|
.eachAsync(async (sticker) => { |
||||
|
const stickerFilename = path.join(job.data.stickerPath, `sticker-${sticker._id}.json`); |
||||
|
await fs.promises.writeFile(stickerFilename, JSON.stringify(sticker, null, 2)); |
||||
|
|
||||
|
if (sticker.original && sticker.original.bucket && sticker.orignal.key && sticker.encoded.type) { |
||||
|
const originalExt = mime.getExtension(sticker.original.type); |
||||
|
const originalFilename = path.join(job.data.stickerMediaPath, `sticker-${sticker._id}.original.${originalExt}`); |
||||
|
await minioService.downloadFile({ |
||||
|
bucket: sticker.original.bucket, |
||||
|
key: sticker.original.key, |
||||
|
filePath: originalFilename, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (sticker.encoded && sticker.encoded.bucket && sticker.encoded.key && sticker.encoded.type) { |
||||
|
const encodedExt = mime.getExtension(sticker.encoded.type); |
||||
|
const encodedFilename = path.join(job.data.stickerMediaPath, `sticker-${sticker._id}.encoded.${encodedExt}`); |
||||
|
await minioService.downloadFile({ |
||||
|
bucket: sticker.encoded.bucket, |
||||
|
key: sticker.encoded.key, |
||||
|
filePath: encodedFilename, |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async archiveUserImages (job) { |
||||
|
const SiteImage = mongoose.model('Image'); |
||||
|
const { image: imageService } = this.dtp.services; |
||||
|
|
||||
|
job.data.imagePath = path.join(job.data.workPath, 'images'); |
||||
|
await fs.promises.mkdir(job.data.imagePath, { recursive: true }); |
||||
|
|
||||
|
this.log.info('archiving user images', { |
||||
|
user: { |
||||
|
_id: job.data.user._id, |
||||
|
username: job.data.user.username, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
await SiteImage |
||||
|
.find({ owner: job.data.user._id }) |
||||
|
.cursor() |
||||
|
.eachAsync(async (image) => { |
||||
|
try { |
||||
|
let imageExt = mime.getExtension(image.type); |
||||
|
const imageFilename = path.join(job.data.imagePath, `image-${image._id}.${imageExt}`); |
||||
|
const metadataFilename = path.join(job.data.imagePath, `image-${image._id}.metadata.json`); |
||||
|
|
||||
|
await imageService.downloadImage(image, imageFilename); |
||||
|
await fs.promises.writeFile(metadataFilename, JSON.stringify(image.metadata, null, 2)); |
||||
|
|
||||
|
} catch (error) { |
||||
|
this.log.error('failed to download image', { |
||||
|
image: { _id: image._id }, |
||||
|
error, |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async createArchiveFile (job) { |
||||
|
const { minio: minioService } = this.dtp.services; |
||||
|
try { |
||||
|
job.data.zipFilename = path.join(job.data.archivePath, `user-${job.data.userId}.zip`); |
||||
|
const zipArgs = [ |
||||
|
'-r', '-9', |
||||
|
job.data.zipFilename, |
||||
|
`${job.data.userId}`, |
||||
|
]; |
||||
|
const options = { |
||||
|
cwd: job.data.archivePath, |
||||
|
encoding: 'utf8', |
||||
|
}; |
||||
|
await execFile('/usr/bin/zip', zipArgs, options); |
||||
|
|
||||
|
const zipFileStat = await fs.promises.stat(job.data.zipFilename); |
||||
|
this.log.info('zip archive created', { size: zipFileStat.size }); |
||||
|
|
||||
|
job.data.archiveFile = { |
||||
|
bucket: process.env.MINIO_ADMIN_BUCKET, |
||||
|
key: `/user-archive/user-${job.data.userId}.zip`, |
||||
|
}; |
||||
|
|
||||
|
const response = await minioService.uploadFile({ |
||||
|
bucket: job.data.archiveFile.bucket, |
||||
|
key: job.data.archiveFile.key, |
||||
|
filePath: job.data.zipFilename, |
||||
|
metadata: { |
||||
|
job: { |
||||
|
id: job.id, |
||||
|
}, |
||||
|
user: job.data.user, |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
this.log.info('creating user archive record', { etag: response.etag, size: zipFileStat.size }); |
||||
|
const UserArchive = mongoose.model('UserArchive'); |
||||
|
await UserArchive.create({ |
||||
|
created: job.data.startTime, |
||||
|
user: { |
||||
|
_id: job.data.userId, |
||||
|
username: job.data.user.username, |
||||
|
email: job.data.fullUser.email, |
||||
|
}, |
||||
|
archive: { |
||||
|
bucket: job.data.archiveFile.bucket, |
||||
|
key: job.data.archiveFile.key, |
||||
|
etag: response.etag, |
||||
|
size: zipFileStat.size, |
||||
|
} |
||||
|
}); |
||||
|
} catch (error) { |
||||
|
this.log.error('failed to create archive .zip file', { |
||||
|
user: { |
||||
|
_id: job.data.userId, |
||||
|
username: job.data.user.username, |
||||
|
}, |
||||
|
}); |
||||
|
throw error; |
||||
|
} finally { |
||||
|
try { |
||||
|
await fs.promises.rm(job.data.zipFilename, { force: true }); |
||||
|
} catch (error) { |
||||
|
this.log.error('failed to remove temp .zip file', { error }); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = ArchiveUserLocalJob; |
Loading…
Reference in new issue