This repository has been archived by the owner on Sep 17, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2efca85
commit 852f02a
Showing
14 changed files
with
293 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export interface Config { | ||
maxProfilePictureBytes: number; | ||
allowedMimes?: string[]; | ||
} | ||
|
||
export const DEFAULT_MIME_TYPES = [ | ||
"image/jpeg", | ||
"image/png", | ||
]; |
22 changes: 22 additions & 0 deletions
22
modules/users/db/migrations/20240401001004_add_pfps/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
-- AlterTable | ||
ALTER TABLE "User" ADD COLUMN "pfpId" UUID; | ||
|
||
-- CreateTable | ||
CREATE TABLE "Pfp" ( | ||
"uploadId" UUID NOT NULL, | ||
"fileId" UUID NOT NULL, | ||
"url" TEXT NOT NULL, | ||
"urlExpiry" TIMESTAMP(3) NOT NULL, | ||
"userId" UUID NOT NULL, | ||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
"updatedAt" TIMESTAMP(3) NOT NULL, | ||
"finishedAt" TIMESTAMP(3), | ||
|
||
CONSTRAINT "Pfp_pkey" PRIMARY KEY ("uploadId") | ||
); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "Pfp_userId_key" ON "Pfp"("userId"); | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "Pfp" ADD CONSTRAINT "Pfp_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; |
8 changes: 8 additions & 0 deletions
8
modules/users/db/migrations/20240401002555_remove_file_id/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/* | ||
Warnings: | ||
- You are about to drop the column `fileId` on the `Pfp` table. All the data in the column will be lost. | ||
*/ | ||
-- AlterTable | ||
ALTER TABLE "Pfp" DROP COLUMN "fileId"; |
16 changes: 16 additions & 0 deletions
16
...0240418032247_remove_pfp_table_rely_on_uploads_module_to_presign_every_time/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/* | ||
Warnings: | ||
- You are about to drop the column `pfpId` on the `User` table. All the data in the column will be lost. | ||
- You are about to drop the `Pfp` table. If the table is not empty, all the data it contains will be lost. | ||
*/ | ||
-- DropForeignKey | ||
ALTER TABLE "Pfp" DROP CONSTRAINT "Pfp_userId_fkey"; | ||
|
||
-- AlterTable | ||
ALTER TABLE "User" DROP COLUMN "pfpId", | ||
ADD COLUMN "avatarUploadId" UUID; | ||
|
||
-- DropTable | ||
DROP TABLE "Pfp"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { ScriptContext, RuntimeError } from "../_gen/scripts/set_pfp.ts"; | ||
import { User } from "../utils/types.ts"; | ||
import { withPfpUrl } from "../utils/pfp.ts"; | ||
|
||
export interface Request { | ||
uploadId: string; | ||
userToken: string; | ||
} | ||
|
||
export interface Response { | ||
user: User; | ||
} | ||
|
||
export async function run( | ||
ctx: ScriptContext, | ||
req: Request, | ||
): Promise<Response> { | ||
// Authenticate/rate limit because this is a public route | ||
await ctx.modules.rateLimit.throttlePublic({ period: 60, requests: 5 }); | ||
const { userId } = await ctx.modules.users.authenticateUser({ userToken: req.userToken }); | ||
|
||
// Complete the upload in the `uploads` module | ||
await ctx.modules.uploads.complete({ uploadId: req.uploadId }); | ||
|
||
// Delete the old uploaded profile picture and replace it with the new one | ||
const user = await ctx.db.$transaction(async (db) => { | ||
// If there is an existing profile picture, delete it | ||
const oldUser = await db.user.findFirst({ | ||
where: { id: userId }, | ||
}); | ||
|
||
// (This means that `users.authenticateUser` is broken!) | ||
if (!oldUser) { | ||
throw new RuntimeError( | ||
"internal_error", | ||
{ | ||
meta: "Existing user not found", | ||
}, | ||
); | ||
} | ||
|
||
if (oldUser.avatarUploadId) { | ||
await ctx.modules.uploads.delete({ uploadId: oldUser.avatarUploadId }); | ||
} | ||
|
||
// Update the user upload ID | ||
const user = await db.user.update({ | ||
where: { | ||
id: userId, | ||
}, | ||
data: { | ||
avatarUploadId: req.uploadId, | ||
}, | ||
select: { | ||
id: true, | ||
username: true, | ||
avatarUploadId: true, | ||
createdAt: true, | ||
updatedAt: true, | ||
}, | ||
}); | ||
|
||
if (!user) { | ||
throw new RuntimeError("internal_error", { cause: "User not found" }); | ||
} | ||
|
||
return await withPfpUrl( | ||
ctx, | ||
user, | ||
user.avatarUploadId, | ||
); | ||
}); | ||
|
||
return { user }; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { ScriptContext, RuntimeError } from "../_gen/scripts/start_pfp_upload.ts"; | ||
import { DEFAULT_MIME_TYPES } from "../config.ts"; | ||
|
||
export interface Request { | ||
mime: string; | ||
contentLength: string; | ||
userToken: string; | ||
} | ||
|
||
export interface Response { | ||
url: string; | ||
uploadId: string; | ||
} | ||
|
||
export async function run( | ||
ctx: ScriptContext, | ||
req: Request, | ||
): Promise<Response> { | ||
// Authenticate/rate limit because this is a public route | ||
await ctx.modules.rateLimit.throttlePublic({ period: 60, requests: 5 }); | ||
const { userId } = await ctx.modules.users.authenticateUser({ userToken: req.userToken }); | ||
|
||
// Ensure at least the MIME type says it is an image | ||
const allowedMimes = ctx.userConfig.allowedMimes ?? DEFAULT_MIME_TYPES; | ||
if (!allowedMimes.includes(req.mime)) { | ||
throw new RuntimeError( | ||
"invalid_mime_type", | ||
{ cause: `MIME type ${req.mime} is not an allowed image type` }, | ||
); | ||
} | ||
|
||
// Ensure the file is within the maximum configured size for a PFP | ||
if (BigInt(req.contentLength) > ctx.userConfig.maxProfilePictureBytes) { | ||
throw new RuntimeError( | ||
"file_too_large", | ||
{ cause: `File is too large (${req.contentLength} bytes)` }, | ||
); | ||
} | ||
|
||
// Prepare the upload to get the presigned URL | ||
const { upload: presigned } = await ctx.modules.uploads.prepare({ | ||
files: [ | ||
{ | ||
path: `profile-picture`, | ||
contentLength: req.contentLength, | ||
mime: req.mime, | ||
multipart: false, | ||
}, | ||
], | ||
}); | ||
|
||
return { | ||
url: presigned.files[0].presignedUrls[0].url, | ||
uploadId: presigned.id, | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { test, TestContext } from "../_gen/test.ts"; | ||
import { faker } from "https://deno.land/x/[email protected]/mod.ts"; | ||
import { assertEquals } from "https://deno.land/[email protected]/assert/assert_equals.ts"; | ||
import { assertExists } from "https://deno.land/[email protected]/assert/assert_exists.ts"; | ||
|
||
test("e2e", async (ctx: TestContext) => { | ||
const imageReq = await fetch("https://picsum.photos/200/300"); | ||
const imageData = new Uint8Array(await imageReq.arrayBuffer()); | ||
|
||
|
||
const { user } = await ctx.modules.users.createUser({ | ||
username: faker.internet.userName(), | ||
}); | ||
|
||
const { token } = await ctx.modules.users.createUserToken({ | ||
userId: user.id, | ||
}); | ||
|
||
const { url, uploadId } = await ctx.modules.users.startProfilePictureUpload({ | ||
mime: imageReq.headers.get("Content-Type") ?? "image/jpeg", | ||
contentLength: imageData.length.toString(), | ||
userToken: token.token, | ||
}); | ||
|
||
// Upload the profile picture | ||
await fetch(url, { | ||
method: "PUT", | ||
body: imageData, | ||
}); | ||
|
||
// Set the profile picture | ||
await ctx.modules.users.setProfilePicture({ | ||
uploadId, | ||
userToken: token.token, | ||
}); | ||
|
||
// Get PFP from URL | ||
const { users: [{ profilePictureUrl }] } = await ctx.modules.users.getUser({ userIds: [user.id] }); | ||
assertExists(profilePictureUrl); | ||
|
||
// Get PFP from URL | ||
const getPfpFromUrl = await fetch(profilePictureUrl); | ||
const pfp = new Uint8Array(await getPfpFromUrl.arrayBuffer()); | ||
assertEquals(pfp, imageData); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { ModuleContext } from "../_gen/mod.ts"; | ||
import { User } from "./types.ts"; | ||
|
||
const EXPIRY_SECS = 60 * 60 * 24; // 1 day | ||
|
||
export const withPfpUrl = async <T extends ModuleContext>( | ||
ctx: T, | ||
user: Omit<User, "profilePictureUrl">, | ||
uploadId: string | null, | ||
) => { | ||
if (!uploadId) { | ||
return { | ||
...user, | ||
profilePictureUrl: null, | ||
}; | ||
} | ||
|
||
const { files: [{ url }] } = await ctx.modules.uploads.getPublicFileUrls({ | ||
files: [{ uploadId, path: "profile-picture" }], | ||
validSecs: EXPIRY_SECS, | ||
}); | ||
|
||
return { | ||
...user, | ||
profilePictureUrl: url, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,4 +3,5 @@ export interface User { | |
username: string; | ||
createdAt: Date; | ||
updatedAt: Date; | ||
profilePictureUrl: string | null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters