diff --git a/package.json b/package.json index 4a19c3c..ca0a9dd 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,14 @@ "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "lucide-react": "^0.378.0", + "mime-types": "^2.1.35", "mongoose": "^8.4.0", "next": "^14.1.2", "react": "^18", "react-dom": "^18" }, "devDependencies": { + "@types/mime-types": "^2.1.4", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6b507c..4cf64a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ dependencies: lucide-react: specifier: ^0.378.0 version: 0.378.0(react@18.2.0) + mime-types: + specifier: ^2.1.35 + version: 2.1.35 mongoose: specifier: ^8.4.0 version: 8.4.0 @@ -37,6 +40,9 @@ dependencies: version: 18.2.0(react@18.2.0) devDependencies: + '@types/mime-types': + specifier: ^2.1.4 + version: 2.1.4 '@types/node': specifier: ^20 version: 20.11.28 @@ -2322,6 +2328,13 @@ packages: } dev: true + /@types/mime-types@2.1.4: + resolution: + { + integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w== + } + dev: true + /@types/node@20.11.28: resolution: { @@ -5000,6 +5013,24 @@ packages: picomatch: 2.3.1 dev: true + /mime-db@1.52.0: + resolution: + { + integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + } + engines: { node: '>= 0.6' } + dev: false + + /mime-types@2.1.35: + resolution: + { + integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + } + engines: { node: '>= 0.6' } + dependencies: + mime-db: 1.52.0 + dev: false + /mimic-fn@2.1.0: resolution: { diff --git a/src/app/api/drop/route.ts b/src/app/api/drop/route.ts index 6f77b59..f76fd6c 100644 --- a/src/app/api/drop/route.ts +++ b/src/app/api/drop/route.ts @@ -3,11 +3,10 @@ import { dbConnect } from '@/lib/db'; import FileModel from '@/lib/models/file'; import { getObject } from '@/lib/s3'; -export async function GET(req: NextRequest) { - const { searchParams } = new URL(req.url); - const id = searchParams.get('id'); +export async function POST(req: NextRequest) { + const body = (await req.json()) as { id: string }; - if (!id) { + if (!body.id) { return NextResponse.json( { error: 'Missing request body parameters' }, { status: 400 } @@ -17,9 +16,9 @@ export async function GET(req: NextRequest) { try { await dbConnect(); - const file = await FileModel.findById(id); + const file = await FileModel.findById(body.id); - if (!file || new Date().getDate() - file.toJSON().updatedAt.getDate() > 5) { + if (!file || file.toJSON().expires.getTime() - Date.now() <= 0) { return NextResponse.json({ error: 'File not found' }, { status: 404 }); } diff --git a/src/app/s/[id]/page.tsx b/src/app/s/[id]/page.tsx index 7bd8ade..f5006a8 100644 --- a/src/app/s/[id]/page.tsx +++ b/src/app/s/[id]/page.tsx @@ -1,4 +1,8 @@ +import { notFound } from 'next/navigation'; +import { extension as mimeExtention } from 'mime-types'; +import Link from 'next/link'; import { Download } from 'lucide-react'; +import { formatBytes } from '@/lib/utils'; import { Card, CardContent, @@ -8,14 +12,33 @@ import { CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; +import type { DropAPIRespData } from '@/types'; + +export const dynamic = 'force-dynamic'; + +type DetailsPageProps = { + params: { + id: string; + }; +}; + +export default async function DetailsPage({ params }: DetailsPageProps) { + const resp = await fetch(`${process.env.NEXT_PUBLIC_URL}/api/drop`, { + method: 'POST', + body: JSON.stringify({ + id: params.id + }) + }); + if (!resp.ok) return notFound(); + + const { success: data } = (await resp.json()) as DropAPIRespData; -export default function DetailsPage() { return (

File shared with you

- + File Details @@ -26,26 +49,28 @@ export default function DetailsPage() {
Name: - Document.pdf + {data.name}
Type: - PDF + {mimeExtention(data.type)}
Size: - 1.2 MB + {formatBytes(data.size)}
- Expires in: - 12 hr + Expires: + {new Date(data.expires).toLocaleString()}
-
diff --git a/src/lib/models/file.ts b/src/lib/models/file.ts index 6d9e216..59347d9 100644 --- a/src/lib/models/file.ts +++ b/src/lib/models/file.ts @@ -1,25 +1,30 @@ import mongoose from 'mongoose'; -const fileSchema = new mongoose.Schema( - { - name: { - type: String, - required: true - }, - size: { - type: Number, - required: true - }, - type: { - type: String, - required: true - }, - key: { - type: String, - required: true - } +const fileSchema = new mongoose.Schema({ + name: { + type: String, + required: true }, - { timestamps: true } -); + size: { + type: Number, + required: true + }, + type: { + type: String, + required: true + }, + key: { + type: String, + required: true + }, + expires: { + type: Date, + default: new Date(new Date().setDate(new Date().getDate() + 1)) // +24 hrs + }, + created_at: { + type: Date, + default: new Date() + } +}); export default mongoose.model('File', fileSchema); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9ad0df4..2757f40 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -4,3 +4,10 @@ import { twMerge } from 'tailwind-merge'; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } + +export function formatBytes(size: number) { + const i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024)); + return ( + +(size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB'][i] + ); +} diff --git a/src/types.ts b/src/types.ts index fe85259..225474e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,9 +11,11 @@ export type UploadAPIRespData = { size: number; type: string; key: string; + expires: Date; url: string; - createdAt: Date; - updatedAt: Date; + created_at: Date; }; error: string; }; + +export type DropAPIRespData = Pick;