Skip to content

Commit

Permalink
update link routes and add shared_access post api to get the user det…
Browse files Browse the repository at this point in the history
…ails from shared linkl
  • Loading branch information
parwatcodes committed Dec 23, 2024
1 parent d050394 commit d564702
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 70 deletions.
2 changes: 1 addition & 1 deletion Client/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ model Link {
friendlyName String? @unique
linkUrl String @unique
isPublic Boolean @default(false)
password String?
password String? // used for SharingOptions
expirationTime DateTime?
hasSharingOptions Boolean @default(false)
updatedAt DateTime @updatedAt @db.Timestamptz(6)
Expand Down
32 changes: 28 additions & 4 deletions Client/src/app/api/links/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import prisma from '@lib/prisma';
import { NextRequest, NextResponse } from 'next/server';
import { authenticate } from '@lib/middleware/authenticate';
import LinkService from '@/services/linkService';
import bcryptjs from 'bcryptjs';

export async function GET(req: NextRequest): Promise<NextResponse> {
try {
Expand All @@ -14,8 +15,21 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
return createErrorResponse('link Id is required.', 400);
}

const signedUrl = await LinkService.getFileFromLink(linkIdFromParams);
return NextResponse.json({ message: 'Link URL generated', data: { signedUrl } }, { status: 200 });
const link = await LinkService.getLink(linkIdFromParams);
if (!link) {
return createErrorResponse('Link not found.', 404);
}

if (link.expirationTime && new Date(link.expirationTime) <= new Date()) {
return NextResponse.json({ message: 'Link is expired' }, { status: 410 });
}

if (!link.hasSharingOptions && link.isPublic) {
const signedUrl = await LinkService.getFileFromLink(linkIdFromParams);
return NextResponse.json({ message: 'Link URL generated', data: { signedUrl } }, { status: 200 });
} else {
return NextResponse.json({ message: 'Link has sharing options enabled', data: { link } }, { status: 200 });
}
} catch (error) {
return createErrorResponse('Server error.', 500, error);
}
Expand All @@ -24,21 +38,31 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
export async function POST(req: NextRequest): Promise<NextResponse> {
try {
const userId = await authenticate(req);
const { documentId, friendlyName, isPublic, emailRequired, passwordRequired, password, linkUrl, expirationTime } = await req.json();
const { documentId, friendlyName, isPublic, password, expirationTime, hasSharingOptions } = await req.json();

if (!documentId) {
return createErrorResponse('Document ID is required.', 400);
}

const { linkUrl, linkId } = LinkService.generateLinkDetails();

if (expirationTime && new Date(expirationTime) < new Date()) {
return createErrorResponse('Expiration time cannot be in the past.', 400);
}

const hashedPassword = password ? await bcryptjs.hash(password, 10) : null;

const newLink = await prisma.link.create({
data: {
userId,
linkId,
linkUrl,
documentId,
isPublic: isPublic,
password: password || null,
password: hashedPassword,
friendlyName: friendlyName || "",
expirationTime: expirationTime || null,
hasSharingOptions: hasSharingOptions || false,
},
});

Expand Down
61 changes: 61 additions & 0 deletions Client/src/app/api/links/shared_access.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import bcryptjs from 'bcryptjs';
import prisma from '@lib/prisma';
import LinkService from '@/services/linkService';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(req: NextRequest): Promise<NextResponse> {
try {
const { linkId, first_name, last_name, email, password } = await req.json();

if (!linkId || !first_name || !last_name || !email || !password) {
return createErrorResponse('Link ID, firstName, lastName, email and password are required.', 400);
}

const link = await LinkService.getLink(linkId);
if (!link) {
return createErrorResponse('Link not found.', 404);
}

if (!link.hasSharingOptions) {
return createErrorResponse('This link does not require sharing options.', 400);
}

const isPasswordValid = await validatePassword(password, link.password as string);
if (!isPasswordValid) {
return createErrorResponse('Invalid password.', 403);
}

await logLinkRecipient(linkId, first_name, last_name, email);

const signedUrl = await LinkService.getFileFromLink(linkId);
return NextResponse.json({ message: 'Link URL generated', data: { signedUrl } }, { status: 200 });
} catch (error) {
return createErrorResponse('Server error.', 500, error);
}
}

function createErrorResponse(message: string, status: number, details?: any) {
if (Array.isArray(details) && details.length === 2) {
const [errorCode, errorMessage] = details;
console.error(`[${new Date().toISOString()}] ${errorMessage}`, details);
return NextResponse.json({ error: errorMessage, details }, { status: errorCode });
} else {
console.error(`[${new Date().toISOString()}] ${message}`, details);
return NextResponse.json({ error: message, details }, { status });
}
}

async function validatePassword(providedPassword: string, storedPassword: string) {
return bcryptjs.compare(providedPassword, storedPassword);
}

async function logLinkRecipient(linkId: string, first_name: string, last_name: string, email: string) {
return prisma.linkRecipients.create({
data: {
linkId,
first_name,
last_name,
email,
},
});
}
63 changes: 0 additions & 63 deletions Client/src/app/doc-preview/[linkId]/page.tsx

This file was deleted.

14 changes: 12 additions & 2 deletions Client/src/services/linkService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,26 @@ import { SupabaseProvider } from '@/providers/storage/supabase/supabaseProvider'

export default class LinkService {

static generateLinkUrl(host: string): string {
static generateLinkDetails(): { linkUrl: string, linkId: string; } {
const uniqueId = uuidv4();
const HOST = process.env.HOST || 'http://localhost:3000';

return `${host}/${uniqueId}`;
return {
linkId: uniqueId,
linkUrl: `${HOST}/${uniqueId}`
};
}

static async deleteLink(linkId: string): Promise<void> {
return;
}

static async getLink(linkId: string) {
return prisma.link.findUnique({
where: { linkId },
});
}

static async getFileFromLink(linkId: string): Promise<string> {
const bucketName = 'documents';
let link = await prisma.link.findUnique({
Expand Down

0 comments on commit d564702

Please sign in to comment.