Skip to content

Commit 4084a6b

Browse files
committed
ban british users from nsfw
1 parent 9755ee8 commit 4084a6b

File tree

5 files changed

+170
-34
lines changed

5 files changed

+170
-34
lines changed

src/lib/auth/middleware.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ export const requireAuth = createMiddleware<{
6161

6262
await next()
6363
} catch (error) {
64-
console.error('Auth error:', error)
65-
return c.json({ success: false, error: 'Authentication middlewarefailed' }, 401)
64+
return c.json({ success: false, error: 'Authentication middleware failed' }, 401)
6665
}
6766
})
6867

@@ -72,7 +71,7 @@ export const requireAdminOrContributor = createMiddleware<{
7271
}>(async (ctx, next) => {
7372
const user = ctx.get('fullUser')
7473
if (!user || (user.role !== 'admin' && user.role !== 'contributor')) {
75-
throw new Error('Forbidden: Only admin or contributor can access this route')
74+
return ctx.json({ success: false, message: 'Forbidden: Only admin or contributor can access this route' }, 403)
7675
}
7776
await next()
7877
})
@@ -81,9 +80,9 @@ export const requireAdmin = createMiddleware<{
8180
Bindings: Env
8281
Variables: AuthVariables
8382
}>(async (ctx, next) => {
84-
const user = ctx.get('user')
83+
const user = ctx.get('fullUser')
8584
if (!user || user.role !== 'admin') {
86-
throw new Error('Forbidden: Only admin can access this route')
85+
return ctx.json({ success: false, message: 'Forbidden: Only admin can access this route' }, 403)
8786
}
8887
await next()
8988
})

src/lib/db/connection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as schema from '~/lib/db/schema'
66

77
class LoggerWrapper implements Logger {
88
logQuery(query: string, params: unknown[]): void {
9-
console.log(`[DRIZZLE]: Query: ${query}, Parameters: ${params ?? 'none'}`)
9+
// console.log(`[DRIZZLE]: Query: ${query}, Parameters: ${params ?? 'none'}`)
1010
}
1111
}
1212

src/routes/asset/approval-queue.ts

Lines changed: 152 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { AppHandler } from '~/lib/handler'
33
import { getConnection } from '~/lib/db/connection'
44
import { createRoute } from '@hono/zod-openapi'
55
import { GenericResponses } from '~/lib/response-schemas'
6-
import { asset, user } from '~/lib/db/schema'
7-
import { eq, inArray } from 'drizzle-orm'
6+
import { asset, user, game, category, tag, assetToTag } from '~/lib/db/schema'
7+
import { eq, inArray, desc } from 'drizzle-orm'
88
import { requireAuth, requireAdmin } from '~/lib/auth/middleware'
99

1010
const responseSchema = z.object({
@@ -13,12 +13,35 @@ const responseSchema = z.object({
1313
z.object({
1414
id: z.string(),
1515
name: z.string(),
16+
gameId: z.string(),
17+
categoryId: z.string(),
18+
extension: z.string(),
1619
status: z.string(),
1720
uploadedBy: z.object({
1821
id: z.string(),
1922
username: z.string().nullable(),
2023
image: z.string().nullable(),
2124
}),
25+
game: z.object({
26+
id: z.string(),
27+
slug: z.string(),
28+
name: z.string(),
29+
lastUpdated: z.string(),
30+
assetCount: z.number(),
31+
}),
32+
category: z.object({
33+
id: z.string(),
34+
name: z.string(),
35+
slug: z.string(),
36+
}),
37+
tags: z.array(
38+
z.object({
39+
id: z.string(),
40+
name: z.string(),
41+
slug: z.string(),
42+
color: z.string().nullable(),
43+
}),
44+
),
2245
}),
2346
),
2447
})
@@ -42,10 +65,25 @@ const approvalQueueRoute = createRoute({
4265
},
4366
})
4467

68+
const paramsSchema = z.object({
69+
id: z.string().openapi({
70+
param: {
71+
description: 'The asset ID',
72+
in: 'path',
73+
name: 'id',
74+
required: true,
75+
},
76+
example: 'asset_123',
77+
}),
78+
})
79+
4580
const approveRoute = createRoute({
46-
path: '/:id/approve',
81+
path: '/{id}/approve',
4782
method: 'post',
4883
summary: 'Approve asset',
84+
request: {
85+
params: paramsSchema,
86+
},
4987
description: 'Approve an asset. Admin only.',
5088
tags: ['Asset'],
5189
responses: {
@@ -58,9 +96,12 @@ const approveRoute = createRoute({
5896
})
5997

6098
const denyRoute = createRoute({
61-
path: '/:id/deny',
99+
path: '/{id}/deny',
62100
method: 'post',
63101
summary: 'Deny asset',
102+
request: {
103+
params: paramsSchema,
104+
},
64105
description: 'Deny an asset. Admin only.',
65106
tags: ['Asset'],
66107
responses: {
@@ -76,37 +117,104 @@ export const AssetApprovalQueueRoute = (handler: AppHandler) => {
76117
handler.use('/approval-queue', requireAuth, requireAdmin)
77118
handler.openapi(approvalQueueRoute, async ctx => {
78119
const { drizzle } = getConnection(ctx.env)
79-
const pendingAssets = await drizzle.select().from(asset).where(eq(asset.status, 'pending'))
120+
const pendingAssets = await drizzle
121+
.select({
122+
id: asset.id,
123+
name: asset.name,
124+
downloadCount: asset.downloadCount,
125+
viewCount: asset.viewCount,
126+
size: asset.size,
127+
extension: asset.extension,
128+
status: asset.status,
129+
createdAt: asset.createdAt,
130+
gameId: game.id,
131+
gameSlug: game.slug,
132+
gameName: game.name,
133+
gameLastUpdated: game.lastUpdated,
134+
gameAssetCount: game.assetCount,
135+
categoryId: category.id,
136+
categoryName: category.name,
137+
categorySlug: category.slug,
138+
isSuggestive: asset.isSuggestive,
139+
uploadedBy: asset.uploadedBy,
140+
})
141+
.from(asset)
142+
.innerJoin(game, eq(asset.gameId, game.id))
143+
.innerJoin(category, eq(asset.categoryId, category.id))
144+
.innerJoin(user, eq(asset.uploadedBy, user.id))
145+
.where(eq(asset.status, 'pending'))
146+
.orderBy(desc(asset.createdAt))
147+
148+
const assetTags = await drizzle
149+
.select({
150+
tagId: tag.id,
151+
tagName: tag.name,
152+
tagSlug: tag.slug,
153+
tagColor: tag.color,
154+
})
155+
.from(assetToTag)
156+
.innerJoin(tag, eq(assetToTag.tagId, tag.id))
157+
.where(
158+
inArray(
159+
assetToTag.assetId,
160+
pendingAssets.map(a => a.id),
161+
),
162+
)
163+
80164
const uploaderIds = pendingAssets.map(a => a.uploadedBy)
81-
const uploaders =
82-
uploaderIds.length > 0
83-
? await drizzle
84-
.select({
85-
id: user.id,
86-
username: user.username,
87-
image: user.image,
88-
})
89-
.from(user)
90-
.where(inArray(user.id, uploaderIds))
91-
: []
165+
const uploaders = await drizzle
166+
.select({
167+
id: user.id,
168+
username: user.username,
169+
image: user.image,
170+
})
171+
.from(user)
172+
.where(inArray(user.id, uploaderIds))
173+
92174
const uploaderMap = Object.fromEntries(uploaders.map(u => [u.id, u]))
93-
const assets = pendingAssets.map(a => ({
175+
176+
const formattedAssets = pendingAssets.map(a => ({
94177
id: a.id,
95178
name: a.name,
96179
status: a.status,
97-
uploadedBy: uploaderMap[a.uploadedBy] || { id: a.uploadedBy, username: null, image: null },
180+
gameId: a.gameId,
181+
categoryId: a.categoryId,
182+
extension: a.extension,
183+
uploadedBy: uploaderMap[a.uploadedBy]!,
184+
game: {
185+
id: a.gameId,
186+
slug: a.gameSlug,
187+
name: a.gameName,
188+
lastUpdated: a.gameLastUpdated,
189+
assetCount: a.gameAssetCount,
190+
},
191+
category: {
192+
id: a.categoryId,
193+
name: a.categoryName,
194+
slug: a.categorySlug,
195+
},
196+
tags: assetTags.map(t => ({
197+
id: t.tagId,
198+
name: t.tagName,
199+
slug: t.tagSlug,
200+
color: t.tagColor,
201+
})),
98202
}))
99-
return ctx.json({ success: true, assets }, 200)
203+
204+
return ctx.json({ success: true, assets: formattedAssets }, 200)
100205
})
101206
}
102207

103208
export const AssetApproveRoute = (handler: AppHandler) => {
104-
handler.use('/:id/approve', requireAuth, requireAdmin)
209+
handler.use('/{id}/approve', requireAuth, requireAdmin)
105210
handler.openapi(approveRoute, async ctx => {
106211
const user = ctx.get('user')
107212

108-
const id = ctx.req.param('id')
213+
if (!user) {
214+
return ctx.json({ success: false, message: 'User context failed' }, 401)
215+
}
109216

217+
const id = ctx.req.param('id')
110218
const { drizzle } = getConnection(ctx.env)
111219

112220
const [foundAsset] = await drizzle.select().from(asset).where(eq(asset.id, id))
@@ -117,18 +225,29 @@ export const AssetApproveRoute = (handler: AppHandler) => {
117225

118226
await drizzle.update(asset).set({ status: 'approved' }).where(eq(asset.id, id))
119227

228+
const file = await ctx.env.CDN.get(`limbo/${foundAsset.id}.${foundAsset.extension}`)
229+
230+
if (!file) {
231+
return ctx.json({ success: false, message: 'Asset file not found' }, 404)
232+
}
233+
234+
await ctx.env.CDN.put(`asset/${foundAsset.id}.${foundAsset.extension}`, file.body)
235+
await ctx.env.CDN.delete(`limbo/${foundAsset.id}.${foundAsset.extension}`)
236+
120237
if (ctx.env.DISCORD_WEBHOOK) {
121238
try {
122239
await fetch(ctx.env.DISCORD_WEBHOOK, {
123240
method: 'POST',
241+
headers: { 'Content-Type': 'application/json' },
124242
body: JSON.stringify({
125243
content: null,
126244
embeds: [
127245
{
128-
description: `Approved ${foundAsset.name} [${foundAsset.extension}]`,
246+
description: `Approved [${foundAsset.name}](https://wanderer.moe/asset/${foundAsset.id}) [.${foundAsset.extension.toUpperCase()}]`,
129247
color: 3669788,
130248
author: {
131249
name: user.username,
250+
icon_url: user.image || undefined,
132251
},
133252
footer: {
134253
text: `${foundAsset.gameId} - ${foundAsset.categoryId}`,
@@ -149,10 +268,14 @@ export const AssetApproveRoute = (handler: AppHandler) => {
149268
}
150269

151270
export const AssetDenyRoute = (handler: AppHandler) => {
152-
handler.use('/:id/deny', requireAuth, requireAdmin)
271+
handler.use('/{id}/deny', requireAuth, requireAdmin)
153272
handler.openapi(denyRoute, async ctx => {
154273
const user = ctx.get('user')
155274

275+
if (!user) {
276+
return ctx.json({ success: false, message: 'User context failed' }, 401)
277+
}
278+
156279
const { drizzle } = getConnection(ctx.env)
157280
const id = ctx.req.param('id')
158281

@@ -162,20 +285,24 @@ export const AssetDenyRoute = (handler: AppHandler) => {
162285
return ctx.json({ success: false, message: 'Asset not found' }, 404)
163286
}
164287

165-
await drizzle.update(asset).set({ status: 'denied' }).where(eq(asset.id, id))
288+
await drizzle.delete(asset).where(eq(asset.id, id))
289+
290+
await ctx.env.CDN.delete(`limbo/${foundAsset.id}.${foundAsset.extension}`)
166291

167292
if (ctx.env.DISCORD_WEBHOOK) {
168293
try {
169294
await fetch(ctx.env.DISCORD_WEBHOOK, {
170295
method: 'POST',
296+
headers: { 'Content-Type': 'application/json' },
171297
body: JSON.stringify({
172298
content: null,
173299
embeds: [
174300
{
175-
description: `Denied ${foundAsset.name} [${foundAsset.extension}]`,
301+
description: `Denied ${foundAsset.name} [.${foundAsset.extension.toUpperCase()}]`,
176302
color: 16734039,
177303
author: {
178304
name: user.username,
305+
icon_url: user.image || undefined,
179306
},
180307
footer: {
181308
text: `${foundAsset.gameId} - ${foundAsset.categoryId}`,

src/routes/asset/id.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { z } from '@hono/zod-openapi'
22
import { AppHandler } from '~/lib/handler'
33
import { getConnection } from '~/lib/db/connection'
4-
import { eq } from 'drizzle-orm'
4+
import { and, eq } from 'drizzle-orm'
55
import { asset, assetToTag, category, game, tag, user } from '~/lib/db/schema'
66
import { createRoute } from '@hono/zod-openapi'
77
import { GenericResponses } from '~/lib/response-schemas'
@@ -84,6 +84,8 @@ export const AssetIdRoute = (handler: AppHandler) => {
8484
const { id } = ctx.req.valid('param')
8585
const { drizzle } = getConnection(ctx.env)
8686

87+
const isGB = ctx.req.header('cf-ipcountry') === 'GB'
88+
8789
try {
8890
const assetResult = await drizzle
8991
.select({
@@ -108,7 +110,7 @@ export const AssetIdRoute = (handler: AppHandler) => {
108110
.from(asset)
109111
.innerJoin(game, eq(asset.gameId, game.id))
110112
.innerJoin(category, eq(asset.categoryId, category.id))
111-
.where(eq(asset.id, id))
113+
.where(and(eq(asset.id, id), isGB ? eq(asset.isSuggestive, false) : undefined))
112114
.limit(1)
113115

114116
if (assetResult.length === 0) {

src/routes/asset/search.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ export const AssetSearchRoute = (handler: AppHandler) => {
173173
handler.openapi(openRoute, async ctx => {
174174
const query = ctx.req.valid('query')
175175

176+
const isGB = ctx.req.header('cf-ipcountry') === 'GB'
177+
176178
const { drizzle } = getConnection(ctx.env)
177179

178180
const page = query.page ? parseInt(query.page) : 1
@@ -267,7 +269,13 @@ export const AssetSearchRoute = (handler: AppHandler) => {
267269
.from(asset)
268270
.innerJoin(game, eq(asset.gameId, game.id))
269271
.innerJoin(category, eq(asset.categoryId, category.id))
270-
.where(and(conditions.length > 0 ? and(...conditions) : undefined, eq(asset.status, 'approved')))
272+
.where(
273+
and(
274+
conditions.length > 0 ? and(...conditions) : undefined,
275+
eq(asset.status, 'approved'),
276+
isGB ? eq(asset.isSuggestive, false) : undefined,
277+
),
278+
)
271279
.orderBy(sortDirection(sortColumn))
272280

273281
const countQuery = drizzle

0 commit comments

Comments
 (0)