Skip to content

Commit

Permalink
upload banner/avatar, rekognition support
Browse files Browse the repository at this point in the history
  • Loading branch information
dromzeh committed Mar 21, 2024
1 parent 726d52d commit 006d36f
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/v2/routes/asset/get-comment-replies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const GetCommentsRepliesRoute = (handler: AppHandler) => {
comment: assetComments.comment,
createdAt: assetComments.createdAt,
hasReplies: sql`EXISTS (SELECT 1 FROM assetComments AS ac WHERE ac.parent_comment_id = ${assetComments.id})`,
likes: sql`COUNT(${assetCommentsLikes.commentId})`,
likes: sql<number>`COUNT(${assetCommentsLikes.commentId})`,
})
.from(assetComments)
.where(eq(assetComments.parentCommentId, commentId))
Expand Down
13 changes: 13 additions & 0 deletions src/v2/routes/asset/upload-asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { createRoute } from "@hono/zod-openapi"
import { GenericResponses } from "@/v2/lib/response-schemas"
import { z } from "@hono/zod-openapi"
import { generateID } from "@/v2/lib/oslo"
import { CheckLabels } from "@/v2/lib/helpers/check-image-tags"

const AcceptedImageType = "image/png"
const MaxFileSize = 5 * 1024 * 1024
Expand Down Expand Up @@ -134,6 +135,18 @@ export const UploadAssetRoute = (handler: AppHandler) =>
)
}

const labels = await CheckLabels(ctx, asset)

if (labels) {
return ctx.json(
{
success: false,
message: "Image contains potentially suggestive content.",
},
400
)
}

const { drizzle } = getConnection(ctx.env)

const randomId = generateID()
Expand Down
5 changes: 5 additions & 0 deletions src/v2/routes/auth/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { UserAllCurrentSessionsRoute } from "./get-all-sessions"
import { LogoutCurrentSessionRoute } from "./logout-current-session"
import { ValidateSessionRoute } from "./validate-current-session"
import { InvalidateSessionRoute } from "./invalidate-session"
import { UploadAvatarRoute } from "./upload-avatar"
import { UploadBannerRoute } from "./upload-banner"

const handler = new OpenAPIHono<{ Bindings: Bindings; Variables: Variables }>()

Expand All @@ -16,4 +18,7 @@ InvalidateSessionRoute(handler)
UserAllCurrentSessionsRoute(handler)
LogoutCurrentSessionRoute(handler)

UploadAvatarRoute(handler)
UploadBannerRoute(handler)

export default handler
117 changes: 117 additions & 0 deletions src/v2/routes/auth/upload-avatar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { AppHandler } from "../handler"
import { AuthSessionManager } from "@/v2/lib/managers/auth/user-session-manager"
import { createRoute } from "@hono/zod-openapi"
import { GenericResponses } from "@/v2/lib/response-schemas"
import { z } from "@hono/zod-openapi"
import { CheckLabels } from "@/v2/lib/helpers/check-image-tags"
import { generateID } from "@/v2/lib/oslo"
import { getConnection } from "@/v2/db/turso"
import { authUser } from "@/v2/db/schema"
import { eq } from "drizzle-orm"

const responseSchema = z.object({
success: z.literal(true),
})

const requestBodySchema = z.object({
avatar: z
.any()
.openapi({
description: "The image of the avatar to upload.",
example: "avatar",
})
.refine((files) => files?.length == 1, "An image is required.")
.refine(
(files) => files?.[0]?.size <= 5 * 1024 * 1024,
`Max file size is 5MB)`
)
.refine(
(files) => files?.[0]?.type === "image/png",
`Only image/png is accepted.`
),
})

const openRoute = createRoute({
path: "/upload/avatar",
method: "post",
summary: "Upload an avatar",
description: "Upload a new avatar, png only.",
tags: ["Auth"],
request: {
body: {
content: {
"multipart/form-data": {
schema: requestBodySchema,
},
},
},
},
responses: {
200: {
description: "The uploaded avatar",
content: {
"application/json": {
schema: responseSchema,
},
},
},
...GenericResponses,
},
})

export const UploadAvatarRoute = (handler: AppHandler) => {
handler.openapi(openRoute, async (ctx) => {
const { avatar } = ctx.req.valid("form")

const authSessionManager = new AuthSessionManager(ctx)

const { user } = await authSessionManager.validateSession()

if (!user) {
return ctx.json(
{
success: false,
message: "Unauthorized",
},
401
)
}

const labels = await CheckLabels(ctx, avatar)

if (labels) {
return ctx.json(
{
success: false,
message: "Image contains potentially inappropriate content",
},
400
)
}

const { drizzle } = await getConnection(ctx.env)

if (user.avatarUrl) {
await ctx.env.FILES_BUCKET.delete(user.avatarUrl)
}

const { key } = await ctx.env.FILES_BUCKET.put(
`/avatars/${user.id}/${generateID(12)}.png`,
avatar.content
)

await drizzle
.update(authUser)
.set({
avatarUrl: key,
})
.where(eq(authUser.id, user.id))

return ctx.json(
{
success: true,
},
200
)
})
}
117 changes: 117 additions & 0 deletions src/v2/routes/auth/upload-banner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { AppHandler } from "../handler"
import { AuthSessionManager } from "@/v2/lib/managers/auth/user-session-manager"
import { createRoute } from "@hono/zod-openapi"
import { GenericResponses } from "@/v2/lib/response-schemas"
import { z } from "@hono/zod-openapi"
import { CheckLabels } from "@/v2/lib/helpers/check-image-tags"
import { generateID } from "@/v2/lib/oslo"
import { getConnection } from "@/v2/db/turso"
import { authUser } from "@/v2/db/schema"
import { eq } from "drizzle-orm"

const responseSchema = z.object({
success: z.literal(true),
})

const requestBodySchema = z.object({
banner: z
.any()
.openapi({
description: "The image of the banner to upload.",
example: "banner",
})
.refine((files) => files?.length == 1, "An image is required.")
.refine(
(files) => files?.[0]?.size <= 5 * 1024 * 1024,
`Max file size is 5MB)`
)
.refine(
(files) => files?.[0]?.type === "image/png",
`Only image/png is accepted.`
),
})

const openRoute = createRoute({
path: "/upload/banner",
method: "post",
summary: "Upload a banner",
description: "Upload a new banner, png only, supporter only.",
tags: ["Auth"],
request: {
body: {
content: {
"multipart/form-data": {
schema: requestBodySchema,
},
},
},
},
responses: {
200: {
description: "The uploaded banner",
content: {
"application/json": {
schema: responseSchema,
},
},
},
...GenericResponses,
},
})

export const UploadBannerRoute = (handler: AppHandler) => {
handler.openapi(openRoute, async (ctx) => {
const { banner } = ctx.req.valid("form")

const authSessionManager = new AuthSessionManager(ctx)

const { user } = await authSessionManager.validateSession()

if (!user || user.plan !== "supporter") {
return ctx.json(
{
success: false,
message: "Unauthorized",
},
401
)
}

const labels = await CheckLabels(ctx, banner)

if (labels) {
return ctx.json(
{
success: false,
message: "Image contains potentially inappropriate content",
},
400
)
}

const { drizzle } = await getConnection(ctx.env)

if (user.bannerUrl) {
await ctx.env.FILES_BUCKET.delete(user.bannerUrl)
}

const { key } = await ctx.env.FILES_BUCKET.put(
`/banners/${user.id}/${generateID(12)}.png`,
banner.content
)

await drizzle
.update(authUser)
.set({
bannerUrl: key,
})
.where(eq(authUser.id, user.id))

return ctx.json(
{
success: true,
},
200
)
})
}
2 changes: 1 addition & 1 deletion src/v2/routes/category/all-categories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createRoute } from "@hono/zod-openapi"
import { z } from "@hono/zod-openapi"
import { selectAssetCategorySchema } from "@/v2/db/schema"

export const responseSchema = z.object({
const responseSchema = z.object({
success: z.literal(true),
categories: selectAssetCategorySchema.array(),
})
Expand Down
4 changes: 2 additions & 2 deletions src/v2/routes/category/create-category.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { GenericResponses } from "@/v2/lib/response-schemas"
import { z } from "@hono/zod-openapi"
import { selectAssetCategorySchema } from "@/v2/db/schema"

export const requestBodySchema = z.object({
const requestBodySchema = z.object({
name: z.string().min(3).max(32).openapi({
description: "The name of the asset category.",
example: "splash-art",
Expand All @@ -19,7 +19,7 @@ export const requestBodySchema = z.object({
}),
})

export const responseSchema = z.object({
const responseSchema = z.object({
success: z.literal(true),
assetCategory: selectAssetCategorySchema,
})
Expand Down

0 comments on commit 006d36f

Please sign in to comment.