Skip to content

Commit

Permalink
add upload status check
Browse files Browse the repository at this point in the history
  • Loading branch information
rajdeep-ghosh committed Jul 28, 2024
1 parent efe92c9 commit 1bc7932
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 14 deletions.
82 changes: 77 additions & 5 deletions src/app/api/drop/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { NextResponse } from 'next/server';
import { eq, lt, sql } from 'drizzle-orm';
import { eq, lt, or, sql } from 'drizzle-orm';
import { generateErrorMessage } from 'zod-error';

import { createFileReqSchema, getFileReqSchema } from '@/lib/api/schema/drop';
import {
createFileReqSchema,
getFileReqSchema,
updateFileReqSchema
} from '@/lib/api/schema/drop';
import { generateFileKey } from '@/lib/utils';
import db from '@/lib/db';
import { filesTable } from '@/lib/db/schema';
Expand Down Expand Up @@ -31,7 +35,7 @@ async function POST(req: NextRequest) {
}

const ip = req.ip ?? '127.0.0.1';
const { limit, remaining, reset } = await ratelimit.upload.limit(ip);
const { limit, remaining, reset } = await ratelimit.upload.create.limit(ip);
const ratelimitHeaders = {
'RateLimit-Limit': limit.toString(),
'RateLimit-Remaining': remaining.toString(),
Expand Down Expand Up @@ -86,6 +90,67 @@ async function POST(req: NextRequest) {
}
}

async function PATCH(req: NextRequest) {
const body = updateFileReqSchema.safeParse(await req.json());

if (!body.success) {
const errMsg = generateErrorMessage(body.error.issues, {
maxErrors: 1,
delimiter: { component: ': ' },
code: { enabled: false },
path: { enabled: true, type: 'objectNotation', label: '' },
message: { enabled: true, label: '' }
});

return NextResponse.json(
{ status: 'error', message: errMsg },
{ status: 400 }
);
}

const ip = req.ip ?? '127.0.0.1';
const { limit, remaining, reset } = await ratelimit.upload.update.limit(ip);
const ratelimitHeaders = {
'RateLimit-Limit': limit.toString(),
'RateLimit-Remaining': remaining.toString(),
'RateLimit-Reset': reset.toString()
};

if (remaining === 0) {
return NextResponse.json(
{ status: 'error', message: 'Rate limit exceeded' },
{ status: 429, headers: ratelimitHeaders }
);
}

try {
if (body.data.success) {
await db
.update(filesTable)
.set({ uploadStatus: 'UPLOADED', updatedAt: sql`now()` })
.where(eq(filesTable.id, body.data.id));

return NextResponse.json('OK', { headers: ratelimitHeaders });
} else {
return NextResponse.json('Not OK', {
status: 422,
headers: ratelimitHeaders
});
}
} catch (err) {
if (err instanceof Error) {
return NextResponse.json(
{ status: 'error', message: err.message },
{ status: 500, headers: ratelimitHeaders }
);
}
return NextResponse.json(
{ status: 'error', message: 'Something went wrong' },
{ status: 500, headers: ratelimitHeaders }
);
}
}

async function GET(req: NextRequest) {
const searchParams = req.nextUrl.searchParams;

Expand Down Expand Up @@ -167,7 +232,14 @@ async function DELETE(req: NextRequest) {
}

try {
await db.delete(filesTable).where(lt(filesTable.expiresAt, sql`now()`));
await db
.delete(filesTable)
.where(
or(
lt(filesTable.expiresAt, sql`now()`),
eq(filesTable.uploadStatus, 'UPLOADING')
)
);

return NextResponse.json('OK');
} catch (err) {
Expand All @@ -178,4 +250,4 @@ async function DELETE(req: NextRequest) {
}
}

export { POST, GET, DELETE };
export { POST, PATCH, GET, DELETE };
12 changes: 11 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,17 @@ export default function HomePage() {
body: selectedFile
});

if (!fileUploadToS3Res.ok) throw new Error('Upload failed');
const updateFileMetaRes = await fetch('/api/drop', {
method: 'PATCH',
body: JSON.stringify({
id: body.data.id,
success: createFileMetaRes.ok && fileUploadToS3Res.ok
})
});

if (!fileUploadToS3Res.ok || !updateFileMetaRes.ok) {
throw new Error('Upload failed');
}

setFileData(body.data);
toast({
Expand Down
7 changes: 6 additions & 1 deletion src/lib/api/schema/drop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ const createFileReqSchema = z.object({
type: z.string().min(1, 'Not supported')
});

const updateFileReqSchema = z.object({
id: z.string().uuid('Invalid id'),
success: z.boolean()
});

const getFileReqSchema = z.string().uuid('Invalid id');

export { createFileReqSchema, getFileReqSchema };
export { createFileReqSchema, updateFileReqSchema, getFileReqSchema };
7 changes: 7 additions & 0 deletions src/lib/db/migrations/0001_demonic_chronomancer.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
DO $$ BEGIN
CREATE TYPE "public"."upload_status_enum" AS ENUM('UPLOADING', 'UPLOADED');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
ALTER TABLE "files" ADD COLUMN "upload_status" "upload_status_enum" DEFAULT 'UPLOADING' NOT NULL;
96 changes: 96 additions & 0 deletions src/lib/db/migrations/meta/0001_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{
"id": "2d74a558-ebdf-4036-b7ec-2ea56727149e",
"prevId": "50157d3b-1c3f-4299-9a55-8863b113d3e1",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.files": {
"name": "files",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"size": {
"name": "size",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"type": {
"name": "type",
"type": "varchar(100)",
"primaryKey": false,
"notNull": true
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"upload_status": {
"name": "upload_status",
"type": "upload_status_enum",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'UPLOADING'"
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"files_key_unique": {
"name": "files_key_unique",
"nullsNotDistinct": false,
"columns": ["key"]
}
}
}
},
"enums": {
"public.upload_status_enum": {
"name": "upload_status_enum",
"schema": "public",
"values": ["UPLOADING", "UPLOADED"]
}
},
"schemas": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
7 changes: 7 additions & 0 deletions src/lib/db/migrations/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
"when": 1720729437310,
"tag": "0000_loving_spencer_smythe",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1722181068575,
"tag": "0001_demonic_chronomancer",
"breakpoints": true
}
]
}
10 changes: 10 additions & 0 deletions src/lib/db/schema/file.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
import {
integer,
pgEnum,
pgTable,
text,
timestamp,
uuid,
varchar
} from 'drizzle-orm/pg-core';

const uploadStatusEnum = pgEnum('upload_status_enum', [
'UPLOADING',
'UPLOADED'
]);

const files = pgTable('files', {
id: uuid('id').primaryKey().defaultRandom(),
name: text('name').notNull(),
size: integer('size').notNull(),
type: varchar('type', { length: 100 }).notNull(),
key: text('key').unique().notNull(),
uploadStatus: uploadStatusEnum('upload_status')
.notNull()
.default('UPLOADING'),
expiresAt: timestamp('expires_at')
.notNull()
.$defaultFn(() => new Date(Date.now() + 24 * 60 * 60 * 1000)), // +24 hrs
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow()
});

export { uploadStatusEnum };
export default files;
2 changes: 1 addition & 1 deletion src/lib/db/schema/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default as filesTable } from './file';
export { default as filesTable, uploadStatusEnum } from './file';
20 changes: 14 additions & 6 deletions src/lib/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,20 @@ async function getObject(key: string) {
}

const ratelimit = {
upload: new Ratelimit({
redis: kv,
prefix: 'dropgo-ratelimit:upload',
analytics: true,
limiter: Ratelimit.slidingWindow(12, '1d') // 12 requests per day
}),
upload: {
create: new Ratelimit({
redis: kv,
prefix: 'dropgo-ratelimit:upload:create',
analytics: true,
limiter: Ratelimit.slidingWindow(12, '1d') // 12 requests per day
}),
update: new Ratelimit({
redis: kv,
prefix: 'dropgo-ratelimit:upload:update',
analytics: true,
limiter: Ratelimit.slidingWindow(12, '1d') // 12 requests per day
})
},
download: new Ratelimit({
redis: kv,
prefix: 'dropgo-ratelimit:download',
Expand Down

0 comments on commit 1bc7932

Please sign in to comment.