feat: reverse shares (#86)

* add first concept

* add reverse share funcionality to frontend

* allow creator to limit share expiration

* moved reverse share in seperate module

* add table to manage reverse shares

* delete complete share if reverse share was deleted

* optimize function names

* add db migration

* enable reverse share email notifications

* fix config variable descriptions

* fix migration for new installations
This commit is contained in:
Elias Schneider
2023-01-26 13:44:04 +01:00
committed by GitHub
parent 1ceb07b89e
commit 4a5fb549c6
43 changed files with 1456 additions and 280 deletions

View File

@@ -15,6 +15,7 @@ import { ConfigService } from "src/config/config.service";
import { EmailService } from "src/email/email.service";
import { FileService } from "src/file/file.service";
import { PrismaService } from "src/prisma/prisma.service";
import { ReverseShareService } from "src/reverseShare/reverseShare.service";
import { CreateShareDTO } from "./dto/createShare.dto";
@Injectable()
@@ -25,10 +26,11 @@ export class ShareService {
private emailService: EmailService,
private config: ConfigService,
private jwtService: JwtService,
private reverseShareService: ReverseShareService,
private clamScanService: ClamScanService
) {}
async create(share: CreateShareDTO, user?: User) {
async create(share: CreateShareDTO, user?: User, reverseShareToken?: string) {
if (!(await this.isShareIdAvailable(share.id)).isAvailable)
throw new BadRequestException("Share id already in use");
@@ -39,30 +41,36 @@ export class ShareService {
share.security.password = await argon.hash(share.security.password);
}
// We have to add an exception for "never" (since moment won't like that)
let expirationDate: Date;
if (share.expiration !== "never") {
expirationDate = moment()
.add(
share.expiration.split("-")[0],
share.expiration.split(
"-"
)[1] as moment.unitOfTime.DurationConstructor
)
.toDate();
// Throw error if expiration date is now
if (expirationDate.setMilliseconds(0) == new Date().setMilliseconds(0))
throw new BadRequestException("Invalid expiration date");
// If share is created by a reverse share token override the expiration date
if (reverseShareToken) {
const { shareExpiration } = await this.reverseShareService.getByToken(
reverseShareToken
);
expirationDate = shareExpiration;
} else {
expirationDate = moment(0).toDate();
// We have to add an exception for "never" (since moment won't like that)
if (share.expiration !== "never") {
expirationDate = moment()
.add(
share.expiration.split("-")[0],
share.expiration.split(
"-"
)[1] as moment.unitOfTime.DurationConstructor
)
.toDate();
} else {
expirationDate = moment(0).toDate();
}
}
fs.mkdirSync(`./data/uploads/shares/${share.id}`, {
recursive: true,
});
return await this.prisma.share.create({
const shareTuple = await this.prisma.share.create({
data: {
...share,
expiration: expirationDate,
@@ -75,6 +83,18 @@ export class ShareService {
},
},
});
if (reverseShareToken) {
// Assign share to reverse share token
await this.prisma.reverseShare.update({
where: { token: reverseShareToken },
data: {
shareId: share.id,
},
});
}
return shareTuple;
}
async createZip(shareId: string) {
@@ -96,10 +116,15 @@ export class ShareService {
await archive.finalize();
}
async complete(id: string) {
async complete(id: string, reverseShareToken?: string) {
const share = await this.prisma.share.findUnique({
where: { id },
include: { files: true, recipients: true, creator: true },
include: {
files: true,
recipients: true,
creator: true,
reverseShare: { include: { creator: true } },
},
});
if (await this.isShareCompleted(id))
@@ -118,16 +143,34 @@ export class ShareService {
// Send email for each recepient
for (const recepient of share.recipients) {
await this.emailService.sendMail(
await this.emailService.sendMailToShareRecepients(
recepient.email,
share.id,
share.creator
);
}
if (
share.reverseShare &&
this.config.get("SMTP_ENABLED") &&
share.reverseShare.sendEmailNotification
) {
await this.emailService.sendMailToReverseShareCreator(
share.reverseShare.creator.email,
share.id
);
}
// Check if any file is malicious with ClamAV
this.clamScanService.checkAndRemove(share.id);
if (reverseShareToken) {
await this.prisma.reverseShare.update({
where: { token: reverseShareToken },
data: { used: true },
});
}
return await this.prisma.share.update({
where: { id },
data: { uploadLocked: true },