mirror of
https://github.com/stonith404/pingvin-share.git
synced 2024-07-02 07:20:38 +02:00
feat: allow multiple shares with one reverse share link
This commit is contained in:
parent
edc10b72b7
commit
ccdf8ea3ae
|
@ -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[]
|
loginTokens LoginToken[]
|
||||||
reverseShares ReverseShare[]
|
reverseShares ReverseShare[]
|
||||||
|
|
||||||
totpEnabled Boolean @default(false)
|
totpEnabled Boolean @default(false)
|
||||||
totpVerified Boolean @default(false)
|
totpVerified Boolean @default(false)
|
||||||
totpSecret String?
|
totpSecret String?
|
||||||
resetPasswordToken ResetPasswordToken?
|
resetPasswordToken ResetPasswordToken?
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ model ResetPasswordToken {
|
||||||
expiresAt DateTime
|
expiresAt DateTime
|
||||||
|
|
||||||
userId String @unique
|
userId String @unique
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
}
|
}
|
||||||
|
|
||||||
model Share {
|
model Share {
|
||||||
|
@ -74,7 +74,8 @@ model Share {
|
||||||
creatorId String?
|
creatorId String?
|
||||||
creator User? @relation(fields: [creatorId], references: [id], onDelete: Cascade)
|
creator User? @relation(fields: [creatorId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
reverseShare ReverseShare?
|
reverseShareId String?
|
||||||
|
reverseShare ReverseShare? @relation(fields: [reverseShareId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
security ShareSecurity?
|
security ShareSecurity?
|
||||||
recipients ShareRecipient[]
|
recipients ShareRecipient[]
|
||||||
|
@ -89,13 +90,12 @@ model ReverseShare {
|
||||||
shareExpiration DateTime
|
shareExpiration DateTime
|
||||||
maxShareSize String
|
maxShareSize String
|
||||||
sendEmailNotification Boolean
|
sendEmailNotification Boolean
|
||||||
used Boolean @default(false)
|
remainingUses Int
|
||||||
|
|
||||||
creatorId String
|
creatorId String
|
||||||
creator User @relation(fields: [creatorId], references: [id], onDelete: Cascade)
|
creator User @relation(fields: [creatorId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
shareId String? @unique
|
shares Share[]
|
||||||
share Share? @relation(fields: [shareId], references: [id], onDelete: Cascade)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model ShareRecipient {
|
model ShareRecipient {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IsBoolean, IsString } from "class-validator";
|
import { IsBoolean, IsString, Max, Min } from "class-validator";
|
||||||
|
|
||||||
export class CreateReverseShareDTO {
|
export class CreateReverseShareDTO {
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
|
@ -9,4 +9,8 @@ export class CreateReverseShareDTO {
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
shareExpiration: string;
|
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 { MyShareDTO } from "src/share/dto/myShare.dto";
|
||||||
import { ReverseShareDTO } from "./reverseShare.dto";
|
import { ReverseShareDTO } from "./reverseShare.dto";
|
||||||
|
|
||||||
export class ReverseShareTokenWithShare extends OmitType(ReverseShareDTO, [
|
export class ReverseShareTokenWithShares extends OmitType(ReverseShareDTO, [
|
||||||
"shareExpiration",
|
"shareExpiration",
|
||||||
] as const) {
|
] as const) {
|
||||||
@Expose()
|
@Expose()
|
||||||
|
@ -11,14 +11,17 @@ export class ReverseShareTokenWithShare extends OmitType(ReverseShareDTO, [
|
||||||
|
|
||||||
@Expose()
|
@Expose()
|
||||||
@Type(() => OmitType(MyShareDTO, ["recipients", "hasPassword"] as const))
|
@Type(() => OmitType(MyShareDTO, ["recipients", "hasPassword"] as const))
|
||||||
share: Omit<
|
shares: Omit<
|
||||||
MyShareDTO,
|
MyShareDTO,
|
||||||
"recipients" | "files" | "from" | "fromList" | "hasPassword"
|
"recipients" | "files" | "from" | "fromList" | "hasPassword"
|
||||||
>;
|
>[];
|
||||||
|
|
||||||
fromList(partial: Partial<ReverseShareTokenWithShare>[]) {
|
@Expose()
|
||||||
|
remainingUses: number;
|
||||||
|
|
||||||
|
fromList(partial: Partial<ReverseShareTokenWithShares>[]) {
|
||||||
return partial.map((part) =>
|
return partial.map((part) =>
|
||||||
plainToClass(ReverseShareTokenWithShare, part, {
|
plainToClass(ReverseShareTokenWithShares, part, {
|
||||||
excludeExtraneousValues: true,
|
excludeExtraneousValues: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
|
@ -15,7 +15,7 @@ import { JwtGuard } from "src/auth/guard/jwt.guard";
|
||||||
import { ConfigService } from "src/config/config.service";
|
import { ConfigService } from "src/config/config.service";
|
||||||
import { CreateReverseShareDTO } from "./dto/createReverseShare.dto";
|
import { CreateReverseShareDTO } from "./dto/createReverseShare.dto";
|
||||||
import { ReverseShareDTO } from "./dto/reverseShare.dto";
|
import { ReverseShareDTO } from "./dto/reverseShare.dto";
|
||||||
import { ReverseShareTokenWithShare } from "./dto/reverseShareTokenWithShare";
|
import { ReverseShareTokenWithShares } from "./dto/reverseShareTokenWithShares";
|
||||||
import { ReverseShareOwnerGuard } from "./guards/reverseShareOwner.guard";
|
import { ReverseShareOwnerGuard } from "./guards/reverseShareOwner.guard";
|
||||||
import { ReverseShareService } from "./reverseShare.service";
|
import { ReverseShareService } from "./reverseShare.service";
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ export class ReverseShareController {
|
||||||
@Get()
|
@Get()
|
||||||
@UseGuards(JwtGuard)
|
@UseGuards(JwtGuard)
|
||||||
async getAllByUser(@GetUser() user: User) {
|
async getAllByUser(@GetUser() user: User) {
|
||||||
return new ReverseShareTokenWithShare().fromList(
|
return new ReverseShareTokenWithShares().fromList(
|
||||||
await this.reverseShareService.getAllByUser(user.id)
|
await this.reverseShareService.getAllByUser(user.id)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ export class ReverseShareService {
|
||||||
const reverseShare = await this.prisma.reverseShare.create({
|
const reverseShare = await this.prisma.reverseShare.create({
|
||||||
data: {
|
data: {
|
||||||
shareExpiration: expirationDate,
|
shareExpiration: expirationDate,
|
||||||
|
remainingUses: data.maxUseCount,
|
||||||
maxShareSize: data.maxShareSize,
|
maxShareSize: data.maxShareSize,
|
||||||
sendEmailNotification: data.sendEmailNotification,
|
sendEmailNotification: data.sendEmailNotification,
|
||||||
creatorId,
|
creatorId,
|
||||||
|
@ -60,7 +61,7 @@ export class ReverseShareService {
|
||||||
orderBy: {
|
orderBy: {
|
||||||
shareExpiration: "desc",
|
shareExpiration: "desc",
|
||||||
},
|
},
|
||||||
include: { share: { include: { creator: true } } },
|
include: { shares: { include: { creator: true } } },
|
||||||
});
|
});
|
||||||
|
|
||||||
return reverseShares;
|
return reverseShares;
|
||||||
|
@ -74,9 +75,9 @@ export class ReverseShareService {
|
||||||
if (!reverseShare) return false;
|
if (!reverseShare) return false;
|
||||||
|
|
||||||
const isExpired = new Date() > reverseShare.shareExpiration;
|
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) {
|
async remove(id: string) {
|
||||||
|
|
|
@ -47,6 +47,7 @@ const Body = ({
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
maxShareSize: 104857600,
|
maxShareSize: 104857600,
|
||||||
|
maxUseCount: 1,
|
||||||
sendEmailNotification: false,
|
sendEmailNotification: false,
|
||||||
expiration_num: 1,
|
expiration_num: 1,
|
||||||
expiration_unit: "-days",
|
expiration_unit: "-days",
|
||||||
|
@ -60,6 +61,7 @@ const Body = ({
|
||||||
.createReverseShare(
|
.createReverseShare(
|
||||||
values.expiration_num + values.expiration_unit,
|
values.expiration_num + values.expiration_unit,
|
||||||
values.maxShareSize,
|
values.maxShareSize,
|
||||||
|
values.maxUseCount,
|
||||||
values.sendEmailNotification
|
values.sendEmailNotification
|
||||||
)
|
)
|
||||||
.then(({ link }) => {
|
.then(({ link }) => {
|
||||||
|
@ -132,6 +134,15 @@ const Body = ({
|
||||||
value={form.values.maxShareSize}
|
value={form.values.maxShareSize}
|
||||||
onChange={(number) => form.setFieldValue("maxShareSize", number)}
|
onChange={(number) => form.setFieldValue("maxShareSize", number)}
|
||||||
/>
|
/>
|
||||||
|
<NumberInput
|
||||||
|
min={1}
|
||||||
|
max={1000}
|
||||||
|
precision={0}
|
||||||
|
variant="filled"
|
||||||
|
label="Max use count"
|
||||||
|
description="The maximum number of times this reverse share link can be used"
|
||||||
|
{...form.getInputProps("maxUseCount")}
|
||||||
|
/>
|
||||||
{showSendEmailNotificationOption && (
|
{showSendEmailNotificationOption && (
|
||||||
<Switch
|
<Switch
|
||||||
mt="xs"
|
mt="xs"
|
||||||
|
|
|
@ -13,12 +13,12 @@ export const config = {
|
||||||
|
|
||||||
export async function middleware(request: NextRequest) {
|
export async function middleware(request: NextRequest) {
|
||||||
const routes = {
|
const routes = {
|
||||||
unauthenticated: new Routes(["/auth/signIn", "/auth/resetPassword*", "/"]),
|
unauthenticated: new Routes(["/auth/*", "/"]),
|
||||||
public: new Routes(["/share/*", "/upload/*"]),
|
public: new Routes(["/share/*", "/upload/*"]),
|
||||||
setupStatusRegistered: new Routes(["/auth/*", "/admin/setup"]),
|
setupStatusRegistered: new Routes(["/auth/*", "/admin/setup"]),
|
||||||
admin: new Routes(["/admin/*"]),
|
admin: new Routes(["/admin/*"]),
|
||||||
account: new Routes(["/account/*"]),
|
account: new Routes(["/account/*"]),
|
||||||
disabledRoutes: new Routes([]),
|
disabled: new Routes([]),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get config from backend
|
// Get config from backend
|
||||||
|
@ -46,7 +46,7 @@ export async function middleware(request: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getConfig("ALLOW_REGISTRATION")) {
|
if (!getConfig("ALLOW_REGISTRATION")) {
|
||||||
routes.disabledRoutes.routes.push("/auth/signUp");
|
routes.disabled.routes.push("/auth/signUp");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getConfig("ALLOW_UNAUTHENTICATED_SHARES")) {
|
if (getConfig("ALLOW_UNAUTHENTICATED_SHARES")) {
|
||||||
|
@ -54,14 +54,14 @@ export async function middleware(request: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getConfig("SMTP_ENABLED")) {
|
if (!getConfig("SMTP_ENABLED")) {
|
||||||
routes.disabledRoutes.routes.push("/auth/resetPassword*");
|
routes.disabled.routes.push("/auth/resetPassword*");
|
||||||
}
|
}
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const rules = [
|
const rules = [
|
||||||
// Disabled routes
|
// Disabled routes
|
||||||
{
|
{
|
||||||
condition: routes.disabledRoutes.contains(route),
|
condition: routes.disabled.contains(route),
|
||||||
path: "/",
|
path: "/",
|
||||||
},
|
},
|
||||||
// Setup status
|
// Setup status
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
Accordion,
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
|
@ -54,7 +55,7 @@ const MyShares = () => {
|
||||||
position="bottom"
|
position="bottom"
|
||||||
multiline
|
multiline
|
||||||
width={220}
|
width={220}
|
||||||
label="A reverse share allows you to generate a unique URL for a single-use share for an external user."
|
label="A reverse share allows you to generate a unique URL that allows external users to create a share."
|
||||||
events={{ hover: true, focus: false, touch: true }}
|
events={{ hover: true, focus: false, touch: true }}
|
||||||
>
|
>
|
||||||
<ActionIcon>
|
<ActionIcon>
|
||||||
|
@ -87,8 +88,8 @@ const MyShares = () => {
|
||||||
<Table>
|
<Table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Shares</th>
|
||||||
<th>Visitors</th>
|
<th>Remaining uses</th>
|
||||||
<th>Max share size</th>
|
<th>Max share size</th>
|
||||||
<th>Expires at</th>
|
<th>Expires at</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
@ -97,14 +98,63 @@ const MyShares = () => {
|
||||||
<tbody>
|
<tbody>
|
||||||
{reverseShares.map((reverseShare) => (
|
{reverseShares.map((reverseShare) => (
|
||||||
<tr key={reverseShare.id}>
|
<tr key={reverseShare.id}>
|
||||||
<td>
|
<td style={{ width: 220 }}>
|
||||||
{reverseShare.share ? (
|
{reverseShare.shares.length == 0 ? (
|
||||||
reverseShare.share?.id
|
<Text color="dimmed" size="sm">
|
||||||
|
No shares created yet
|
||||||
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text color="dimmed">No share created yet</Text>
|
<Accordion>
|
||||||
|
<Accordion.Item
|
||||||
|
value="customization"
|
||||||
|
sx={{ borderBottom: "none" }}
|
||||||
|
>
|
||||||
|
<Accordion.Control p={0}>
|
||||||
|
<Text size="sm">
|
||||||
|
{`${reverseShare.shares.length} share${
|
||||||
|
reverseShare.shares.length > 1 ? "s" : ""
|
||||||
|
}`}
|
||||||
|
</Text>
|
||||||
|
</Accordion.Control>
|
||||||
|
<Accordion.Panel>
|
||||||
|
{reverseShare.shares.map((share) => (
|
||||||
|
<Group key={share.id} mb={4}>
|
||||||
|
<Text maw={120} truncate>
|
||||||
|
{share.id}
|
||||||
|
</Text>
|
||||||
|
<ActionIcon
|
||||||
|
color="victoria"
|
||||||
|
variant="light"
|
||||||
|
size={25}
|
||||||
|
onClick={() => {
|
||||||
|
if (window.isSecureContext) {
|
||||||
|
clipboard.copy(
|
||||||
|
`${config.get("APP_URL")}/share/${
|
||||||
|
share.id
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
toast.success(
|
||||||
|
"The share link was copied to the keyboard."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showShareLinkModal(
|
||||||
|
modals,
|
||||||
|
share.id,
|
||||||
|
config.get("APP_URL")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TbLink />
|
||||||
|
</ActionIcon>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Accordion.Panel>
|
||||||
|
</Accordion.Item>
|
||||||
|
</Accordion>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>{reverseShare.share?.views ?? "0"}</td>
|
<td>{reverseShare.remainingUses}</td>
|
||||||
<td>
|
<td>
|
||||||
{byteToHumanSizeString(parseInt(reverseShare.maxShareSize))}
|
{byteToHumanSizeString(parseInt(reverseShare.maxShareSize))}
|
||||||
</td>
|
</td>
|
||||||
|
@ -115,33 +165,6 @@ const MyShares = () => {
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Group position="right">
|
<Group position="right">
|
||||||
{reverseShare.share && (
|
|
||||||
<ActionIcon
|
|
||||||
color="victoria"
|
|
||||||
variant="light"
|
|
||||||
size={25}
|
|
||||||
onClick={() => {
|
|
||||||
if (window.isSecureContext) {
|
|
||||||
clipboard.copy(
|
|
||||||
`${config.get("APP_URL")}/share/${
|
|
||||||
reverseShare.share!.id
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
toast.success(
|
|
||||||
"The share link was copied to the keyboard."
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
showShareLinkModal(
|
|
||||||
modals,
|
|
||||||
reverseShare.share!.id,
|
|
||||||
config.get("APP_URL")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TbLink />
|
|
||||||
</ActionIcon>
|
|
||||||
)}
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
color="red"
|
color="red"
|
||||||
variant="light"
|
variant="light"
|
||||||
|
@ -152,13 +175,14 @@ const MyShares = () => {
|
||||||
children: (
|
children: (
|
||||||
<Text size="sm">
|
<Text size="sm">
|
||||||
Do you really want to delete this reverse share?
|
Do you really want to delete this reverse share?
|
||||||
If you do, the share will be deleted as well.
|
If you do, the associated shares will be deleted
|
||||||
|
as well.
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
confirmProps: {
|
confirmProps: {
|
||||||
color: "red",
|
color: "red",
|
||||||
},
|
},
|
||||||
labels: { confirm: "Confirm", cancel: "Cancel" },
|
labels: { confirm: "Delete", cancel: "Cancel" },
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
shareService.removeReverseShare(reverseShare.id);
|
shareService.removeReverseShare(reverseShare.id);
|
||||||
setReverseShares(
|
setReverseShares(
|
||||||
|
|
|
@ -99,12 +99,14 @@ const uploadFile = async (
|
||||||
const createReverseShare = async (
|
const createReverseShare = async (
|
||||||
shareExpiration: string,
|
shareExpiration: string,
|
||||||
maxShareSize: number,
|
maxShareSize: number,
|
||||||
|
maxUseCount: number,
|
||||||
sendEmailNotification: boolean
|
sendEmailNotification: boolean
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
await api.post("reverseShares", {
|
await api.post("reverseShares", {
|
||||||
shareExpiration,
|
shareExpiration,
|
||||||
maxShareSize: maxShareSize.toString(),
|
maxShareSize: maxShareSize.toString(),
|
||||||
|
maxUseCount,
|
||||||
sendEmailNotification,
|
sendEmailNotification,
|
||||||
})
|
})
|
||||||
).data;
|
).data;
|
||||||
|
|
|
@ -31,7 +31,8 @@ export type MyReverseShare = {
|
||||||
id: string;
|
id: string;
|
||||||
maxShareSize: string;
|
maxShareSize: string;
|
||||||
shareExpiration: Date;
|
shareExpiration: Date;
|
||||||
share?: MyShare;
|
remainingUses: number;
|
||||||
|
shares: MyShare[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShareSecurity = {
|
export type ShareSecurity = {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user