1
0
mirror of https://github.com/stonith404/pingvin-share.git synced 2024-10-02 09:30:10 +02:00

feat: add name property to share (#462)

* add name property to share

* refactor: run formatter

* tests: adapt system tests

* tests: adapt second system test
This commit is contained in:
Elias Schneider 2024-05-03 18:12:26 +03:00 committed by GitHub
parent 0e12ba87bc
commit b717663b5c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 59 additions and 39 deletions

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Share" ADD COLUMN "name" TEXT;

View File

@ -75,6 +75,7 @@ model Share {
id String @id @default(uuid()) id String @id @default(uuid())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
name String?
uploadLocked Boolean @default(false) uploadLocked Boolean @default(false)
isZipReady Boolean @default(false) isZipReady Boolean @default(false)
views Int @default(0) views Int @default(0)

View File

@ -13,7 +13,7 @@ export class ReverseShareTokenWithShares extends OmitType(ReverseShareDTO, [
@Type(() => OmitType(MyShareDTO, ["recipients", "hasPassword"] as const)) @Type(() => OmitType(MyShareDTO, ["recipients", "hasPassword"] as const))
shares: Omit< shares: Omit<
MyShareDTO, MyShareDTO,
"recipients" | "files" | "from" | "fromList" | "hasPassword" "recipients" | "files" | "from" | "fromList" | "hasPassword" | "size"
>[]; >[];
@Expose() @Expose()

View File

@ -18,6 +18,10 @@ export class CreateShareDTO {
@Length(3, 50) @Length(3, 50)
id: string; id: string;
@Length(3, 30)
@IsOptional()
name: string;
@IsString() @IsString()
expiration: string; expiration: string;

View File

@ -6,6 +6,9 @@ export class ShareDTO {
@Expose() @Expose()
id: string; id: string;
@Expose()
name?: string;
@Expose() @Expose()
expiration: Date; expiration: Date;
@ -23,6 +26,9 @@ export class ShareDTO {
@Expose() @Expose()
hasPassword: boolean; hasPassword: boolean;
@Expose()
size: number;
from(partial: Partial<ShareDTO>) { from(partial: Partial<ShareDTO>) {
return plainToClass(ShareDTO, partial, { excludeExtraneousValues: true }); return plainToClass(ShareDTO, partial, { excludeExtraneousValues: true });
} }

View File

@ -214,6 +214,7 @@ export class ShareService {
return shares.map((share) => { return shares.map((share) => {
return { return {
...share, ...share,
size: share.files.reduce((acc, file) => acc + parseInt(file.size), 0),
recipients: share.recipients.map((recipients) => recipients.email), recipients: share.recipients.map((recipients) => recipients.email),
}; };
}); });

View File

@ -432,7 +432,7 @@
" const responseBody = pm.response.json();", " const responseBody = pm.response.json();",
" pm.expect(responseBody).to.have.property(\"id\")", " pm.expect(responseBody).to.have.property(\"id\")",
" pm.expect(responseBody).to.have.property(\"expiration\")", " pm.expect(responseBody).to.have.property(\"expiration\")",
" pm.expect(Object.keys(responseBody).length).be.equal(3)", " pm.expect(Object.keys(responseBody).length).be.equal(4)",
"});", "});",
"" ""
], ],
@ -626,7 +626,7 @@
" const responseBody = pm.response.json();", " const responseBody = pm.response.json();",
" pm.expect(responseBody).to.have.property(\"id\")", " pm.expect(responseBody).to.have.property(\"id\")",
" pm.expect(responseBody).to.have.property(\"expiration\")", " pm.expect(responseBody).to.have.property(\"expiration\")",
" pm.expect(Object.keys(responseBody).length).be.equal(3)", " pm.expect(Object.keys(responseBody).length).be.equal(4)",
"});", "});",
"" ""
], ],

View File

@ -17,13 +17,9 @@ const showShareInformationsModal = (
const t = translateOutsideContext(); const t = translateOutsideContext();
const link = `${appUrl}/s/${share.id}`; const link = `${appUrl}/s/${share.id}`;
let shareSize: number = 0; const formattedShareSize = byteToHumanSizeString(share.size);
for (let file of share.files as FileMetaData[])
shareSize += parseInt(file.size);
const formattedShareSize = byteToHumanSizeString(shareSize);
const formattedMaxShareSize = byteToHumanSizeString(maxShareSize); const formattedMaxShareSize = byteToHumanSizeString(maxShareSize);
const shareSizeProgress = (shareSize / maxShareSize) * 100; const shareSizeProgress = (share.size / maxShareSize) * 100;
const formattedCreatedAt = moment(share.createdAt).format("LLL"); const formattedCreatedAt = moment(share.createdAt).format("LLL");
const formattedExpiration = const formattedExpiration =
@ -42,12 +38,18 @@ const showShareInformationsModal = (
</b> </b>
{share.id} {share.id}
</Text> </Text>
<Text size="sm">
<b>
<FormattedMessage id="account.shares.table.name" />:{" "}
</b>
{share.name || "-"}
</Text>
<Text size="sm"> <Text size="sm">
<b> <b>
<FormattedMessage id="account.shares.table.description" />:{" "} <FormattedMessage id="account.shares.table.description" />:{" "}
</b> </b>
{share.description || "No description"} {share.description || "-"}
</Text> </Text>
<Text size="sm"> <Text size="sm">
@ -75,15 +77,15 @@ const showShareInformationsModal = (
</Text> </Text>
<Flex align="center" justify="center"> <Flex align="center" justify="center">
{shareSize / maxShareSize < 0.1 && ( {share.size / maxShareSize < 0.1 && (
<Text size="xs" style={{ marginRight: "4px" }}> <Text size="xs" style={{ marginRight: "4px" }}>
{formattedShareSize} {formattedShareSize}
</Text> </Text>
)} )}
<Progress <Progress
value={shareSizeProgress} value={shareSizeProgress}
label={shareSize / maxShareSize >= 0.1 ? formattedShareSize : ""} label={share.size / maxShareSize >= 0.1 ? formattedShareSize : ""}
style={{ width: shareSize / maxShareSize < 0.1 ? "70%" : "80%" }} style={{ width: share.size / maxShareSize < 0.1 ? "70%" : "80%" }}
size="xl" size="xl"
radius="xl" radius="xl"
/> />

View File

@ -92,11 +92,16 @@ const CreateUploadModalBody = ({
.matches(new RegExp("^[a-zA-Z0-9_-]*$"), { .matches(new RegExp("^[a-zA-Z0-9_-]*$"), {
message: t("upload.modal.link.error.invalid"), message: t("upload.modal.link.error.invalid"),
}), }),
name: yup
.string()
.transform((value) => value || undefined)
.min(3, t("common.error.too-short", { length: 3 }))
.max(30, t("common.error.too-long", { length: 30 })),
password: yup password: yup
.string() .string()
.transform((value) => value || undefined) .transform((value) => value || undefined)
.min(3) .min(3, t("common.error.too-short", { length: 3 }))
.max(30), .max(30, t("common.error.too-long", { length: 30 })),
maxViews: yup maxViews: yup
.number() .number()
.transform((value) => value || undefined) .transform((value) => value || undefined)
@ -105,6 +110,7 @@ const CreateUploadModalBody = ({
const form = useForm({ const form = useForm({
initialValues: { initialValues: {
name: undefined,
link: generatedLink, link: generatedLink,
recipients: [] as string[], recipients: [] as string[],
password: undefined, password: undefined,
@ -154,6 +160,7 @@ const CreateUploadModalBody = ({
uploadCallback( uploadCallback(
{ {
id: values.link, id: values.link,
name: values.name,
expiration: expirationString, expiration: expirationString,
recipients: values.recipients, recipients: values.recipients,
description: values.description, description: values.description,
@ -308,14 +315,21 @@ const CreateUploadModalBody = ({
<Accordion> <Accordion>
<Accordion.Item value="description" sx={{ borderBottom: "none" }}> <Accordion.Item value="description" sx={{ borderBottom: "none" }}>
<Accordion.Control> <Accordion.Control>
<FormattedMessage id="upload.modal.accordion.description.title" /> <FormattedMessage id="upload.modal.accordion.name-and-description.title" />
</Accordion.Control> </Accordion.Control>
<Accordion.Panel> <Accordion.Panel>
<Stack align="stretch"> <Stack align="stretch">
<TextInput
variant="filled"
placeholder={t(
"upload.modal.accordion.name-and-description.name.placeholder",
)}
{...form.getInputProps("name")}
/>
<Textarea <Textarea
variant="filled" variant="filled"
placeholder={t( placeholder={t(
"upload.modal.accordion.description.placeholder", "upload.modal.accordion.name-and-description.description.placeholder",
)} )}
{...form.getInputProps("description")} {...form.getInputProps("description")}
/> />

View File

@ -307,8 +307,9 @@ export default {
"upload.modal.expires.year-singular": "Year", "upload.modal.expires.year-singular": "Year",
"upload.modal.expires.year-plural": "Years", "upload.modal.expires.year-plural": "Years",
"upload.modal.accordion.description.title": "Description", "upload.modal.accordion.name-and-description.title": "Name and description",
"upload.modal.accordion.description.placeholder": "upload.modal.accordion.name-and-description.name.placeholder": "Name",
"upload.modal.accordion.name-and-description.description.placeholder":
"Note for the recipients of this share", "Note for the recipients of this share",
"upload.modal.accordion.email.title": "Email recipients", "upload.modal.accordion.email.title": "Email recipients",

View File

@ -68,15 +68,12 @@ const MyShares = () => {
<Table> <Table>
<thead> <thead>
<tr> <tr>
<th>
<FormattedMessage id="account.shares.table.id" />
</th>
<th> <th>
<FormattedMessage id="account.shares.table.name" /> <FormattedMessage id="account.shares.table.name" />
</th> </th>
<MediaQuery smallerThan="md" styles={{ display: "none" }}>
<th>
<FormattedMessage id="account.shares.table.description" />
</th>
</MediaQuery>
<th> <th>
<FormattedMessage id="account.shares.table.visitors" /> <FormattedMessage id="account.shares.table.visitors" />
</th> </th>
@ -90,18 +87,7 @@ const MyShares = () => {
{shares.map((share) => ( {shares.map((share) => (
<tr key={share.id}> <tr key={share.id}>
<td>{share.id}</td> <td>{share.id}</td>
<MediaQuery smallerThan="sm" styles={{ display: "none" }}> <td>{share.name}</td>
<td
style={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
maxWidth: "300px",
}}
>
{share.description || ""}
</td>
</MediaQuery>
<td>{share.views}</td> <td>{share.views}</td>
<td> <td>
{moment(share.expiration).unix() === 0 {moment(share.expiration).unix() === 0

View File

@ -91,13 +91,13 @@ const Share = ({ shareId }: { shareId: string }) => {
return ( return (
<> <>
<Meta <Meta
title={t("share.title", { shareId })} title={t("share.title", { shareId: share?.name || shareId })}
description={t("share.description")} description={t("share.description")}
/> />
<Group position="apart" mb="lg"> <Group position="apart" mb="lg">
<Box style={{ maxWidth: "70%" }}> <Box style={{ maxWidth: "70%" }}>
<Title order={3}>{share?.id}</Title> <Title order={3}>{share?.name || share?.id}</Title>
<Text size="sm">{share?.description}</Text> <Text size="sm">{share?.description}</Text>
</Box> </Box>
{share?.files.length > 1 && <DownloadAllButton shareId={shareId} />} {share?.files.length > 1 && <DownloadAllButton shareId={shareId} />}

View File

@ -2,15 +2,18 @@ import User from "./user.type";
export type Share = { export type Share = {
id: string; id: string;
name?: string;
files: any; files: any;
creator: User; creator: User;
description?: string; description?: string;
expiration: Date; expiration: Date;
size: number;
hasPassword: boolean; hasPassword: boolean;
}; };
export type CreateShare = { export type CreateShare = {
id: string; id: string;
name?: string;
description?: string; description?: string;
recipients: string[]; recipients: string[];
expiration: string; expiration: string;