mirror of
https://github.com/swissmakers/swiss-datashare.git
synced 2026-04-11 10:27:01 +02:00
feat: allow multiple shares with one reverse share link
This commit is contained in:
@@ -1,14 +0,0 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ResetPasswordToken" (
|
||||
"token" TEXT NOT NULL PRIMARY KEY,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
CONSTRAINT "ResetPasswordToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- Disable TOTP as secret isn't encrypted anymore
|
||||
UPDATE User SET totpEnabled=false, totpSecret=null, totpVerified=false WHERE totpSecret IS NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ResetPasswordToken_userId_key" ON "ResetPasswordToken"("userId");
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `shareId` on the `ReverseShare` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `used` on the `ReverseShare` table. All the data in the column will be lost.
|
||||
- Added the required column `remainingUses` to the `ReverseShare` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- CreateTable
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "ResetPasswordToken" (
|
||||
"token" TEXT NOT NULL PRIMARY KEY,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expiresAt" DATETIME NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
CONSTRAINT "ResetPasswordToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- Disable TOTP as secret isn't encrypted anymore
|
||||
UPDATE User SET totpEnabled=false, totpSecret=null, totpVerified=false WHERE totpSecret IS NOT NULL;
|
||||
|
||||
-- RedefineTables
|
||||
CREATE TABLE "new_Share" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"uploadLocked" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isZipReady" BOOLEAN NOT NULL DEFAULT false,
|
||||
"views" INTEGER NOT NULL DEFAULT 0,
|
||||
"expiration" DATETIME NOT NULL,
|
||||
"description" TEXT,
|
||||
"removedReason" TEXT,
|
||||
"creatorId" TEXT,
|
||||
"reverseShareId" TEXT,
|
||||
CONSTRAINT "Share_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT "Share_reverseShareId_fkey" FOREIGN KEY ("reverseShareId") REFERENCES "ReverseShare" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO "new_Share" ("createdAt", "creatorId", "description", "expiration", "id", "isZipReady", "removedReason", "uploadLocked", "views", "reverseShareId")
|
||||
SELECT "createdAt", "creatorId", "description", "expiration", "id", "isZipReady", "removedReason", "uploadLocked", "views", (SELECT id FROM ReverseShare WHERE shareId=Share.id)
|
||||
FROM "Share";
|
||||
|
||||
|
||||
DROP TABLE "Share";
|
||||
ALTER TABLE "new_Share" RENAME TO "Share";
|
||||
CREATE TABLE "new_ReverseShare" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"token" TEXT NOT NULL,
|
||||
"shareExpiration" DATETIME NOT NULL,
|
||||
"maxShareSize" TEXT NOT NULL,
|
||||
"sendEmailNotification" BOOLEAN NOT NULL,
|
||||
"remainingUses" INTEGER NOT NULL,
|
||||
"creatorId" TEXT NOT NULL,
|
||||
CONSTRAINT "ReverseShare_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_ReverseShare" ("createdAt", "creatorId", "id", "maxShareSize", "sendEmailNotification", "shareExpiration", "token", "remainingUses") SELECT "createdAt", "creatorId", "id", "maxShareSize", "sendEmailNotification", "shareExpiration", "token", iif("ReverseShare".used, 0, 1) FROM "ReverseShare";
|
||||
DROP TABLE "ReverseShare";
|
||||
ALTER TABLE "new_ReverseShare" RENAME TO "ReverseShare";
|
||||
CREATE UNIQUE INDEX "ReverseShare_token_key" ON "ReverseShare"("token");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ResetPasswordToken_userId_key" ON "ResetPasswordToken"("userId");
|
||||
@@ -22,8 +22,8 @@ model User {
|
||||
loginTokens LoginToken[]
|
||||
reverseShares ReverseShare[]
|
||||
|
||||
totpEnabled Boolean @default(false)
|
||||
totpVerified Boolean @default(false)
|
||||
totpEnabled Boolean @default(false)
|
||||
totpVerified Boolean @default(false)
|
||||
totpSecret String?
|
||||
resetPasswordToken ResetPasswordToken?
|
||||
}
|
||||
@@ -57,7 +57,7 @@ model ResetPasswordToken {
|
||||
expiresAt DateTime
|
||||
|
||||
userId String @unique
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model Share {
|
||||
@@ -74,7 +74,8 @@ model Share {
|
||||
creatorId String?
|
||||
creator User? @relation(fields: [creatorId], references: [id], onDelete: Cascade)
|
||||
|
||||
reverseShare ReverseShare?
|
||||
reverseShareId String?
|
||||
reverseShare ReverseShare? @relation(fields: [reverseShareId], references: [id], onDelete: Cascade)
|
||||
|
||||
security ShareSecurity?
|
||||
recipients ShareRecipient[]
|
||||
@@ -89,13 +90,12 @@ model ReverseShare {
|
||||
shareExpiration DateTime
|
||||
maxShareSize String
|
||||
sendEmailNotification Boolean
|
||||
used Boolean @default(false)
|
||||
remainingUses Int
|
||||
|
||||
creatorId String
|
||||
creator User @relation(fields: [creatorId], references: [id], onDelete: Cascade)
|
||||
|
||||
shareId String? @unique
|
||||
share Share? @relation(fields: [shareId], references: [id], onDelete: Cascade)
|
||||
shares Share[]
|
||||
}
|
||||
|
||||
model ShareRecipient {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IsBoolean, IsString } from "class-validator";
|
||||
import { IsBoolean, IsString, Max, Min } from "class-validator";
|
||||
|
||||
export class CreateReverseShareDTO {
|
||||
@IsBoolean()
|
||||
@@ -9,4 +9,8 @@ export class CreateReverseShareDTO {
|
||||
|
||||
@IsString()
|
||||
shareExpiration: string;
|
||||
|
||||
@Min(1)
|
||||
@Max(1000)
|
||||
maxUseCount: number;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Expose, plainToClass, Type } from "class-transformer";
|
||||
import { MyShareDTO } from "src/share/dto/myShare.dto";
|
||||
import { ReverseShareDTO } from "./reverseShare.dto";
|
||||
|
||||
export class ReverseShareTokenWithShare extends OmitType(ReverseShareDTO, [
|
||||
export class ReverseShareTokenWithShares extends OmitType(ReverseShareDTO, [
|
||||
"shareExpiration",
|
||||
] as const) {
|
||||
@Expose()
|
||||
@@ -11,14 +11,17 @@ export class ReverseShareTokenWithShare extends OmitType(ReverseShareDTO, [
|
||||
|
||||
@Expose()
|
||||
@Type(() => OmitType(MyShareDTO, ["recipients", "hasPassword"] as const))
|
||||
share: Omit<
|
||||
shares: Omit<
|
||||
MyShareDTO,
|
||||
"recipients" | "files" | "from" | "fromList" | "hasPassword"
|
||||
>;
|
||||
>[];
|
||||
|
||||
fromList(partial: Partial<ReverseShareTokenWithShare>[]) {
|
||||
@Expose()
|
||||
remainingUses: number;
|
||||
|
||||
fromList(partial: Partial<ReverseShareTokenWithShares>[]) {
|
||||
return partial.map((part) =>
|
||||
plainToClass(ReverseShareTokenWithShare, part, {
|
||||
plainToClass(ReverseShareTokenWithShares, part, {
|
||||
excludeExtraneousValues: true,
|
||||
})
|
||||
);
|
||||
@@ -15,7 +15,7 @@ import { JwtGuard } from "src/auth/guard/jwt.guard";
|
||||
import { ConfigService } from "src/config/config.service";
|
||||
import { CreateReverseShareDTO } from "./dto/createReverseShare.dto";
|
||||
import { ReverseShareDTO } from "./dto/reverseShare.dto";
|
||||
import { ReverseShareTokenWithShare } from "./dto/reverseShareTokenWithShare";
|
||||
import { ReverseShareTokenWithShares } from "./dto/reverseShareTokenWithShares";
|
||||
import { ReverseShareOwnerGuard } from "./guards/reverseShareOwner.guard";
|
||||
import { ReverseShareService } from "./reverseShare.service";
|
||||
|
||||
@@ -51,7 +51,7 @@ export class ReverseShareController {
|
||||
@Get()
|
||||
@UseGuards(JwtGuard)
|
||||
async getAllByUser(@GetUser() user: User) {
|
||||
return new ReverseShareTokenWithShare().fromList(
|
||||
return new ReverseShareTokenWithShares().fromList(
|
||||
await this.reverseShareService.getAllByUser(user.id)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ export class ReverseShareService {
|
||||
const reverseShare = await this.prisma.reverseShare.create({
|
||||
data: {
|
||||
shareExpiration: expirationDate,
|
||||
remainingUses: data.maxUseCount,
|
||||
maxShareSize: data.maxShareSize,
|
||||
sendEmailNotification: data.sendEmailNotification,
|
||||
creatorId,
|
||||
@@ -60,7 +61,7 @@ export class ReverseShareService {
|
||||
orderBy: {
|
||||
shareExpiration: "desc",
|
||||
},
|
||||
include: { share: { include: { creator: true } } },
|
||||
include: { shares: { include: { creator: true } } },
|
||||
});
|
||||
|
||||
return reverseShares;
|
||||
@@ -74,9 +75,9 @@ export class ReverseShareService {
|
||||
if (!reverseShare) return false;
|
||||
|
||||
const isExpired = new Date() > reverseShare.shareExpiration;
|
||||
const isUsed = reverseShare.used;
|
||||
const remainingUsesExceeded = reverseShare.remainingUses <= 0;
|
||||
|
||||
return !(isExpired || isUsed);
|
||||
return !(isExpired || remainingUsesExceeded);
|
||||
}
|
||||
|
||||
async remove(id: string) {
|
||||
|
||||
Reference in New Issue
Block a user