Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,5 @@ build
.env
# Sentry Config File
.sentryclirc
uploads/*
uploads/*
42 changes: 1 addition & 41 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
@@ -1,41 +1 @@
{
"name": "@apps/backend",
"private": true,
"type": "module",
"scripts": {
"lint": "bunx biome lint --write || true",
"check": "bunx biome check --write || true",
"format": "bunx biome format --write || true",
"dev": "bun run --watch src/index.ts",
"build": "bun run lint && bun run format && bun build src/index.ts --outdir=build --target=bun --minify && bun run sentry:sourcemaps",
"start": "bun run build/index.js",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org womb0comb0 --project product-decoder-backend ./build && sentry-cli sourcemaps upload --org womb0comb0 --project product-decoder-backend ./build"
},
"dependencies": {
"@packages/db": "workspace:*",
"@elysiajs/bearer": "^1.3.0",
"@elysiajs/cors": "^1.3.3",
"@elysiajs/jwt": "^1.3.1",
"@elysiajs/opentelemetry": "^1.3.0",
"@elysiajs/server-timing": "^1.3.0",
"@elysiajs/swagger": "^1.3.0",
"@opentelemetry/resources": "^2.0.1",
"@sentry/cli": "^2.50.2",
"@types/bun": "^1.2.20",
"@types/jsonwebtoken": "^9.0.10",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"cryptr": "^6.3.0",
"dayjs": "^1.11.13",
"elysia": "latest",
"elysia-ip": "^1.0.10",
"elysia-rate-limit": "^4.4.0",
"elysiajs-helmet": "^1.0.2",
"logixlysia": "^5.1.0"
},
"devDependencies": {
"@biomejs/biome": "2.1.1",
"bun-types": "latest",
"prisma": "^6.11.1"
}
}
{ ...same JSON as above... }
41 changes: 41 additions & 0 deletions apps/backend/package.json.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This backup file, along with package.jsonN and package.jsonY, appears to be a temporary file that was accidentally committed. These files should not be part of the repository as they add unnecessary clutter. Please remove them and consider adding a pattern like *.bak to your .gitignore file to prevent this in the future.

"name": "@apps/backend",
"private": true,
"type": "module",
"scripts": {
"lint": "bunx biome lint --write || true",
"check": "bunx biome check --write || true",
"format": "bunx biome format --write || true",
"dev": "bun --watch src/index.ts",
"build": "bun run lint && bun run format && bun build src/index.ts --outdir=build --target=bun --minify && bun run sentry:sourcemaps",
"start": "bun run build/index.js",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org womb0comb0 --project product-decoder-backend ./build && sentry-cli sourcemaps upload --org womb0comb0 --project product-decoder-backend ./build"
},
"dependencies": {
"@packages/db": "workspace:*",
"@elysiajs/bearer": "^1.3.0",
"@elysiajs/cors": "^1.3.3",
"@elysiajs/jwt": "^1.3.1",
"@elysiajs/opentelemetry": "^1.3.0",
"@elysiajs/server-timing": "^1.3.0",
"@elysiajs/swagger": "^1.3.0",
"@opentelemetry/resources": "^2.0.1",
"@sentry/cli": "^2.50.2",
"@types/bun": "^1.2.20",
"@types/jsonwebtoken": "^9.0.10",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"cryptr": "^6.3.0",
"dayjs": "^1.11.13",
"elysia": "latest",
"elysia-ip": "^1.0.10",
"elysia-rate-limit": "^4.4.0",
"elysiajs-helmet": "^1.0.2",
"logixlysia": "^5.1.0"
},
"devDependencies": {
"@biomejs/biome": "2.1.1",
"bun-types": "latest",
"prisma": "^6.11.1"
}
}
46 changes: 46 additions & 0 deletions apps/backend/package.jsonN
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "@apps/backend",
"private": true,
"type": "module",
"scripts": {
"lint": "bunx biome lint --write || true",
"check": "bunx biome check --write || true",
"format": "bunx biome format --write || true",
"dev": "bun --watch src/index.ts",
"build": "tsc",
"start": "bun run build/index.js",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org womb0comb0 --project product-decoder-backend ./build && sentry-cli sourcemaps upload --org womb0comb0 --project
product-decoder-backend ./build"
},
"dependencies": {
"@packages/db": "workspace:*",
"@elysiajs/bearer": "^1.3.0",
"@elysiajs/cors": "^1.3.3",
"@elysiajs/jwt": "^1.3.1",
"@elysiajs/opentelemetry": "^1.3.0",
"@elysiajs/server-timing": "^1.3.0",
"@elysiajs/swagger": "^1.3.0",
"@opentelemetry/resources": "^2.0.1",
"@sentry/cli": "^2.50.2",
"@types/bun": "^1.2.20",
"@types/jsonwebtoken": "^9.0.10",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"cryptr": "^6.3.0",
"dayjs": "^1.11.13",
"elysia": "latest",
"elysia-ip": "^1.0.10",
"elysia-rate-limit": "^4.4.0",
"elysiajs-helmet": "^1.0.2",
"logixlysia": "^5.1.0"
},
"devDependencies": {
"@biomejs/biome": "2.1.1",
"bun-types": "latest",
"prisma": "^6.11.1"
}
}



ø
43 changes: 43 additions & 0 deletions apps/backend/package.jsonY
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@apps/backend",
"private": true,
"type": "module",
"scripts": {
"lint": "bunx biome lint --write || true",
"check": "bunx biome check --write || true",
"format": "bunx biome format --write || true",
"dev": "bun --watch src/index.ts",
"build": "bun run lint && bun run format && bun build src/index.ts --outdir=build --target=bun --minify && bun run sentry:sourcemaps",
"start": "bun run build/index.js",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org womb0comb0 --project product-decoder-backend ./build && sentry-cli sourcemaps upload --org womb0comb0 --project
product-decoder-backend ./build"
},
"dependencies": {
"@packages/db": "workspace:*",
"@elysiajs/bearer": "^1.3.0",
"@elysiajs/cors": "^1.3.3",
"@elysiajs/jwt": "^1.3.1",
"@elysiajs/opentelemetry": "^1.3.0",
"@elysiajs/server-timing": "^1.3.0",
"@elysiajs/swagger": "^1.3.0",
"@opentelemetry/resources": "^2.0.1",
"@sentry/cli": "^2.50.2",
"@types/bun": "^1.2.20",
"@types/jsonwebtoken": "^9.0.10",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"cryptr": "^6.3.0",
"dayjs": "^1.11.13",
"elysia": "latest",
"elysia-ip": "^1.0.10",
"elysia-rate-limit": "^4.4.0",
"elysiajs-helmet": "^1.0.2",
"logixlysia": "^5.1.0"
},
"devDependencies": {
"@biomejs/biome": "2.1.1",
"bun-types": "latest",
"prisma": "^6.11.1"
}
}
'
3 changes: 3 additions & 0 deletions apps/backend/src/config/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import path from 'node:path'
export const BACKEND_ROOT = path.resolve(process.cwd())
export const UPLOAD_DIR = path.join(BACKEND_ROOT, 'uploads')
45 changes: 14 additions & 31 deletions apps/backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,16 @@
import { cors } from '@elysiajs/cors';
import { serverTiming } from '@elysiajs/server-timing';
import { swagger } from '@elysiajs/swagger';
import { Elysia } from 'elysia';
import env from '#helpers/env';
import authRoutes from '#modules/auth/routes';
import meRoutes from '#modules/me/routes';
import userRoutes from '#modules/user/routes';
import { Elysia } from 'elysia'
import { cors } from '@elysiajs/cors'
import { staticPlugin } from '@elysiajs/static'
import { UPLOAD_DIR } from './config/paths'
import { uploadRoutes } from './routes/upload'
import { filesRoutes } from './routes/files'

console.time('⌛ Startup Time');

new Elysia()
.use(swagger())
.use(serverTiming())
const app = new Elysia()
.use(cors())
.group('/api', (app) =>
app
.use(userRoutes)
.use(authRoutes)
.use(meRoutes)
.onError(({ error, ...ctx }) => {
console.log({ ctx });
}),
)
.listen(env.SERVER_PORT, (server) => {
console.timeEnd('⌛ Startup Time');
console.log(`🌱 NODE_ENV: ${env.NODE_ENV || 'development'}`);
console.log(`🍙 Bun Version: ${Bun.version}`);
console.log(`🦊 Elysia.js Version: ${require('elysia/package.json').version}`);
console.log(`🗃️ Prisma Version: ${require('@prisma/client/package.json').version}`);
console.log(`🚀 Server is running at ${server.url}`);
console.log('--------------------------------------------------');
});
.use(staticPlugin({ assets: UPLOAD_DIR, prefix: '/uploads' }))
.use(filesRoutes)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The staticPlugin is already configured to serve files from the /uploads directory. This filesRoutes handler is redundant and creates a potential conflict. The first registered handler will process the request, making this one likely unreachable. It's best to have a single, clear way of serving static files. I recommend removing this route handler and relying solely on staticPlugin.

.use(uploadRoutes)
.get('/health', () => ({ ok: true }))
.listen(process.env.SERVER_PORT ? Number(process.env.SERVER_PORT) : 3001)

console.log(`Backend listening on http://localhost:${app.server?.port}`)
14 changes: 14 additions & 0 deletions apps/backend/src/routes/files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Elysia from 'elysia'
import path from 'node:path'
import fs from 'node:fs'
import { UPLOAD_DIR } from '../config/paths'

export const filesRoutes = new Elysia()
.get('/uploads/:name', ({ params, set }) => {
const filePath = path.join(UPLOAD_DIR, params.name)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The current implementation is vulnerable to path traversal attacks. A malicious user could provide a path like ../../etc/passwd in params.name to access sensitive files outside of the intended uploads directory. You must sanitize the input to ensure the final resolved path is within UPLOAD_DIR.

if (!fs.existsSync(filePath)) {
set.status = 404
return 'Not Found'
}
Comment on lines +9 to +12
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using the synchronous fs.existsSync can block the event loop, which can degrade server performance under load. It's better to use an asynchronous alternative. Bun.file(filePath).exists() is an async option that fits well here.

    if (await Bun.file(filePath).exists()) {
      return new Response(Bun.file(filePath));
    }
    set.status = 404;
    return 'Not Found';

return new Response(Bun.file(filePath))
})
53 changes: 53 additions & 0 deletions apps/backend/src/routes/upload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Elysia, { t } from 'elysia'
import { randomUUID } from 'node:crypto'
import path from 'node:path'
import fs from 'node:fs/promises'

import { UPLOAD_DIR } from '../config/paths'

async function ensureDir(p: string) {
await fs.mkdir(p, { recursive: true })
}

export const uploadRoutes = new Elysia({ prefix: '/api' })
.post(
'/upload',
async ({ body, request, set }) => {
const file = body.image;
if (!file) {
set.status = 400
return { error: 'No file field "image" found.' }
}

if (!file.type?.startsWith('image/')) {
set.status = 415
return { error: 'Only image uploads are allowed.' }
}

const ext = file.name?.split('.').pop() || 'bin'
const id = randomUUID()
const filename = `${id}.${ext}`

await ensureDir(UPLOAD_DIR)
const filepath = path.join(UPLOAD_DIR, filename)
const arrayBuf = await file.arrayBuffer()
await Bun.write(filepath, arrayBuf)

const origin = `${request.headers.get('x-forwarded-proto') || 'http'}://${request.headers.get('host')}`
const url = `${origin}/uploads/${filename}`

return {
id,
name: file.name,
type: file.type,
size: file.size,
url
}
},
{
body: t.Object({
image: t.File({ maxSize: 10 * 1024 * 1024 }) // 10MB
}),
detail: { tags: ['upload'] }
}
)
5 changes: 5 additions & 0 deletions apps/frontend/middleware.disabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { auth as middleware } from "@/lib/auth";

export const config = {
matcher: ["/dashboard/:path*"], // protect dashboard
};
18 changes: 0 additions & 18 deletions apps/frontend/middleware.ts

This file was deleted.

18 changes: 6 additions & 12 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,12 @@
"sideEffects": false,
"type": "module",
"scripts": {
"auth:generate": "bunx @better-auth/cli@latest generate --config ./src/lib/auth/index.ts --y --output ./src/lib/db/schema/auth.schema.ts && bunx prettier --write ./src/lib/db/schema/auth.schema.ts",
"auth:secret": "bunx @better-auth/cli@latest secret",
"build": "bun run next build",
"check": "bun run format && bun run lint && bun run check-types",
"check-types": "bunx tsc --noEmit",
"deps": "bunx taze@latest major -Ilw",
"dev": "bun run next dev --port 3000",
"format": "bunx @biomejs/biome format --write . || true",
"lint": "bunx @biomejs/biome lint . || true",
"start": "bun run next start",
"ui": "bunx shadcn@latest"
},
"dev": "next dev -p 3000",
"build": "next build",
"start": "next start -p 3000",
"lint": "next lint"
},

"browserslist": {
"development": [
"last 1 chrome version",
Expand Down
Empty file added apps/frontend/package.tmp
Empty file.
36 changes: 30 additions & 6 deletions apps/frontend/src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
import ImageUploader from "../../components/ImageUploader";
import Link from "next/link";

export default function DashboardPage() {
return (
<div className="flex flex-col items-center gap-1">
Dashboard index page
<pre className="bg-card text-card-foreground rounded-md border p-1">
app/dashboard/page.tsx
</pre>
</div>
<main className="min-h-screen bg-[#fff7ea]">
{/* Top bar matching Figma */}
<header className="flex items-center justify-between px-8 py-6">
<div className="text-[34px] leading-none font-extrabold text-[#5d63e6]">
<div>Product</div>
<div>Decoder</div>
</div>

<nav className="flex items-center gap-10 text-[20px]">
<Link href="/dashboard" className="text-[#e27d55] font-semibold">Image upload</Link>
<Link href="/saved" className="font-semibold text-black">Saved searches</Link>
<Link href="/" className="font-semibold text-black">Home</Link>
</nav>

<div className="flex items-center gap-4">
<button className="px-5 py-2 rounded-xl bg-[#f8c83b] shadow text-black font-semibold">Log Out</button>
<div className="w-12 h-12 rounded-full bg-[#f1e7ff] flex items-center justify-center">
<UserIcon className="w-6 h-6" />
</div>
</div>
</header>

{/* Big blue panel with dashed drop zone */}
<section className="mt-4">
<ImageUploader />
</section>
</main>
);
}
Loading