diff --git a/apps/web/src/app/(share)/api/share/route.tsx b/apps/web/src/app/(share)/api/share/route.tsx
deleted file mode 100644
index d7984db84..000000000
--- a/apps/web/src/app/(share)/api/share/route.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { ImageResponse } from 'next/server';
-
-export const config = {
- runtime: 'edge',
-};
-
-export async function GET() {
- const [imageData, fontData] = await Promise.all([
- fetch(new URL('../../../../assets/background-pattern.png', import.meta.url)).then((res) =>
- res.arrayBuffer(),
- ),
- fetch(new URL('../../../assets/Caveat-Regular.ttf', import.meta.url)).then((res) =>
- res.arrayBuffer(),
- ),
- ]);
-
- return new ImageResponse(
- (
-
- ),
- {
- width: 1200,
- height: 630,
- fonts: [
- {
- name: 'Caveat',
- data: fontData,
- style: 'italic',
- },
- ],
- },
- );
-}
diff --git a/apps/web/src/app/(share)/share/[shortId]/page.tsx b/apps/web/src/app/(share)/share/[shortId]/page.tsx
new file mode 100644
index 000000000..0c6fb0897
--- /dev/null
+++ b/apps/web/src/app/(share)/share/[shortId]/page.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+
+export type SharePageProps = {
+ params: {
+ shortId?: string;
+ };
+};
+
+export default async function SharePage({ params: { shortId } }: SharePageProps) {
+ console.log(shortId);
+
+ return (
+
+
Share Page
+
+ );
+}
diff --git a/apps/web/src/app/(share)/share/page.tsx b/apps/web/src/app/(share)/share/page.tsx
deleted file mode 100644
index a0309503c..000000000
--- a/apps/web/src/app/(share)/share/page.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from 'react';
-
-export default async function SharePage() {
- return (
-
-
Share Page
-
- );
-}
diff --git a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx
index 71a368da5..9b3b80851 100644
--- a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx
@@ -1,16 +1,16 @@
import Link from 'next/link';
import { notFound } from 'next/navigation';
-import { CheckCircle2, Clock8, Share } from 'lucide-react';
+import { CheckCircle2, Clock8 } from 'lucide-react';
import { match } from 'ts-pattern';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
import { DocumentStatus, FieldType } from '@documenso/prisma/client';
-import { Button } from '@documenso/ui/primitives/button';
import { DownloadButton } from './download-button';
+import { ShareButton } from './share-button';
import { SigningCard } from './signing-card';
export type CompletedSigningPageProps = {
@@ -88,11 +88,7 @@ export default async function CompletedSigningPage({
))}
- {/* TODO: Hook this up */}
-
+
& {
+ recipientId: number;
+ documentId: number;
+};
+
+export const ShareButton = ({ recipientId, documentId }: ShareButtonProps) => {
+ const { mutateAsync: createShareId } = trpc.share.create.useMutation();
+
+ const router = useRouter();
+
+ return (
+
+ );
+};
diff --git a/packages/lib/server-only/share/create-share-id.ts b/packages/lib/server-only/share/create-share-id.ts
new file mode 100644
index 000000000..0d376e283
--- /dev/null
+++ b/packages/lib/server-only/share/create-share-id.ts
@@ -0,0 +1,20 @@
+import { nanoid } from 'nanoid';
+
+import { prisma } from '@documenso/prisma';
+
+export interface CreateSharingIdOptions {
+ documentId: number;
+ recipientId: number;
+}
+
+export const createSharingId = async ({ documentId, recipientId }: CreateSharingIdOptions) => {
+ const result = await prisma.share.create({
+ data: {
+ recipientId,
+ documentId,
+ link: nanoid(),
+ },
+ });
+
+ return result;
+};
diff --git a/packages/prisma/migrations/20230829165148_user_sharing_link/migration.sql b/packages/prisma/migrations/20230829165148_user_sharing_link/migration.sql
new file mode 100644
index 000000000..793d125b3
--- /dev/null
+++ b/packages/prisma/migrations/20230829165148_user_sharing_link/migration.sql
@@ -0,0 +1,20 @@
+-- CreateTable
+CREATE TABLE "Share" (
+ "id" SERIAL NOT NULL,
+ "userId" INTEGER NOT NULL,
+ "link" TEXT NOT NULL,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+ "documentId" INTEGER,
+
+ CONSTRAINT "Share_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Share_link_key" ON "Share"("link");
+
+-- AddForeignKey
+ALTER TABLE "Share" ADD CONSTRAINT "Share_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Share" ADD CONSTRAINT "Share_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE SET NULL ON UPDATE CASCADE;
diff --git a/packages/prisma/migrations/20230829180915_recipient_id_for_user_id/migration.sql b/packages/prisma/migrations/20230829180915_recipient_id_for_user_id/migration.sql
new file mode 100644
index 000000000..51008f8ec
--- /dev/null
+++ b/packages/prisma/migrations/20230829180915_recipient_id_for_user_id/migration.sql
@@ -0,0 +1,16 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `userId` on the `Share` table. All the data in the column will be lost.
+ - Added the required column `recipientId` to the `Share` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- DropForeignKey
+ALTER TABLE "Share" DROP CONSTRAINT "Share_userId_fkey";
+
+-- AlterTable
+ALTER TABLE "Share" DROP COLUMN "userId",
+ADD COLUMN "recipientId" INTEGER NOT NULL;
+
+-- AddForeignKey
+ALTER TABLE "Share" ADD CONSTRAINT "Share_recipientId_fkey" FOREIGN KEY ("recipientId") REFERENCES "Recipient"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma
index b2b86f00a..95b2581ff 100644
--- a/packages/prisma/schema.prisma
+++ b/packages/prisma/schema.prisma
@@ -101,33 +101,16 @@ enum DocumentStatus {
}
model Document {
- id Int @id @default(autoincrement())
- userId Int
- User User @relation(fields: [userId], references: [id], onDelete: Cascade)
- title String
- status DocumentStatus @default(DRAFT)
- Recipient Recipient[]
- Field Field[]
- documentDataId String
- documentData DocumentData @relation(fields: [documentDataId], references: [id], onDelete: Cascade)
- createdAt DateTime @default(now())
- updatedAt DateTime @default(now()) @updatedAt
-
- @@unique([documentDataId])
-}
-
-enum DocumentDataType {
- S3_PATH
- BYTES
- BYTES_64
-}
-
-model DocumentData {
- id String @id @default(cuid())
- type DocumentDataType
- data String
- initialData String
- Document Document?
+ id Int @id @default(autoincrement())
+ created DateTime @default(now())
+ userId Int
+ User User @relation(fields: [userId], references: [id], onDelete: Cascade)
+ title String
+ status DocumentStatus @default(DRAFT)
+ document String
+ Recipient Recipient[]
+ Field Field[]
+ Share Share[]
}
enum ReadStatus {
@@ -159,6 +142,7 @@ model Recipient {
Document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
Field Field[]
Signature Signature[]
+ Share Share[]
@@unique([documentId, email])
}
@@ -201,11 +185,13 @@ model Signature {
Field Field @relation(fields: [fieldId], references: [id], onDelete: Restrict)
}
-model PasswordResetToken {
- id Int @id @default(autoincrement())
- token String @unique
- createdAt DateTime @default(now())
- expiry DateTime
- userId Int
- User User @relation(fields: [userId], references: [id])
+model Share {
+ id Int @id @default(autoincrement())
+ recipientId Int
+ recipent Recipient @relation(fields: [recipientId], references: [id])
+ link String @unique
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ document Document? @relation(fields: [documentId], references: [id])
+ documentId Int?
}
diff --git a/packages/trpc/server/router.ts b/packages/trpc/server/router.ts
index afe3b2b07..d69505d08 100644
--- a/packages/trpc/server/router.ts
+++ b/packages/trpc/server/router.ts
@@ -3,6 +3,7 @@ import { authRouter } from './auth-router/router';
import { documentRouter } from './document-router/router';
import { fieldRouter } from './field-router/router';
import { profileRouter } from './profile-router/router';
+import { shareRouter } from './share-router/router';
import { procedure, router } from './trpc';
export const appRouter = router({
@@ -11,7 +12,7 @@ export const appRouter = router({
profile: profileRouter,
document: documentRouter,
field: fieldRouter,
- admin: adminRouter,
+ share: shareRouter,
});
export type AppRouter = typeof appRouter;
diff --git a/packages/trpc/server/share-router/router.ts b/packages/trpc/server/share-router/router.ts
new file mode 100644
index 000000000..d9001201c
--- /dev/null
+++ b/packages/trpc/server/share-router/router.ts
@@ -0,0 +1,23 @@
+import { TRPCError } from '@trpc/server';
+
+import { createSharingId } from '@documenso/lib/server-only/share/create-share-id';
+
+import { procedure, router } from '../trpc';
+import { ZShareLinkSchema } from './schema';
+
+export const shareRouter = router({
+ create: procedure.input(ZShareLinkSchema).mutation(async ({ input }) => {
+ try {
+ const { documentId, recipientId } = input;
+
+ return await createSharingId({ documentId, recipientId });
+ } catch (err) {
+ console.error(err);
+
+ throw new TRPCError({
+ code: 'BAD_REQUEST',
+ message: 'We were unable to create a sharing link.',
+ });
+ }
+ }),
+});
diff --git a/packages/trpc/server/share-router/schema.ts b/packages/trpc/server/share-router/schema.ts
new file mode 100644
index 000000000..1e063b7b9
--- /dev/null
+++ b/packages/trpc/server/share-router/schema.ts
@@ -0,0 +1,8 @@
+import { z } from 'zod';
+
+export const ZShareLinkSchema = z.object({
+ documentId: z.number(),
+ recipientId: z.number(),
+});
+
+export type ZShareLinkSchema = z.infer;