socialmedia/src/index.ts
2026-03-03 14:08:08 +05:30

170 lines
4.9 KiB
TypeScript

import { Hono } from 'hono'
import { jwt, sign, verify } from 'hono/jwt'
import { drizzle } from 'drizzle-orm/d1'
import { eq, and, inArray, desc } from 'drizzle-orm'
import { usersTable, postsTable, followsTable, likesTable } from './db/schema'
type Bindings = {
DB: D1Database
JWT_SECRET: string
}
const app = new Hono<{ Bindings: Bindings }>()
// Middleware for JWT protection on some routes
app.use('/api/protected/*', async (c, next) => {
const jwtMiddleware = jwt({
secret: c.env.JWT_SECRET || 'supersecret',
alg: 'HS256'
})
return jwtMiddleware(c, next)
})
// --- AUTH ---
app.post('/api/signup', async (c) => {
const { username, password } = await c.req.json()
const db = drizzle(c.env.DB)
try {
const result = await db.insert(usersTable).values({
username,
passwordHash: password, // In a real app, hash this!
}).returning()
return c.json({ success: true, user: result[0] })
} catch (e) {
console.error('Signup error:', e)
return c.json({ error: 'Username taken or database error', details: String(e) }, 400)
}
})
app.post('/api/login', async (c) => {
const { username, password } = await c.req.json()
const db = drizzle(c.env.DB)
const user = await db.select().from(usersTable)
.where(and(eq(usersTable.username, username), eq(usersTable.passwordHash, password)))
.get()
if (!user) return c.json({ error: 'Invalid credentials' }, 401)
const token = await sign({ id: user.id, username: user.username }, c.env.JWT_SECRET || 'supersecret', 'HS256')
return c.json({ token, user })
})
// --- POSTS & FEED ---
app.get('/api/protected/feed', async (c) => {
const payload = c.get('jwtPayload')
const db = drizzle(c.env.DB)
try {
// Get followed users
const followed = await db.select({ id: followsTable.followingId })
.from(followsTable)
.where(eq(followsTable.followerId, payload.id))
.all()
const followedIds = [payload.id, ...followed.map(f => f.id)]
const feed = await db.select({
id: postsTable.id,
content: postsTable.content,
createdAt: postsTable.createdAt,
username: usersTable.username,
userId: usersTable.id
})
.from(postsTable)
.leftJoin(usersTable, eq(postsTable.userId, usersTable.id))
.where(inArray(postsTable.userId, followedIds))
.orderBy(desc(postsTable.createdAt))
.all()
return c.json(feed)
} catch (e) {
console.error('Feed error:', e)
return c.json({ error: 'Failed to fetch feed', details: String(e) }, 500)
}
})
app.post('/api/protected/posts', async (c) => {
const payload = c.get('jwtPayload')
const { content } = await c.req.json()
const db = drizzle(c.env.DB)
try {
const result = await db.insert(postsTable).values({
userId: payload.id,
content,
}).returning()
return c.json(result[0])
} catch (e) {
console.error('Post creation error:', e)
return c.json({ error: 'Failed to create post', details: String(e) }, 500)
}
})
// --- FOLLOW & LIKE ---
app.post('/api/protected/follow/:id', async (c) => {
const payload = c.get('jwtPayload')
const targetId = parseInt(c.req.param('id'))
const db = drizzle(c.env.DB)
await db.insert(followsTable).values({
followerId: payload.id,
followingId: targetId,
}).run()
return c.json({ success: true })
})
app.post('/api/protected/like/:id', async (c) => {
const payload = c.get('jwtPayload')
const postId = parseInt(c.req.param('id'))
const db = drizzle(c.env.DB)
await db.insert(likesTable).values({
userId: payload.id,
postId,
}).run()
return c.json({ success: true })
})
// Serve React App
app.get('*', async (c) => {
return c.html(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Social Media App</title>
${import.meta.env?.PROD ? '' : `
<script type="module">
import RefreshRuntime from "/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__reactRefreshLog = () => {}
</script>
<script type="module" src="/@vite/client"></script>
`}
<style>
body { font-family: sans-serif; background: #f0f2f5; margin: 0; padding: 20px; }
.container { max-width: 600px; margin: auto; }
.card { background: white; padding: 15px; border-radius: 8px; margin-bottom: 10px; box-shadow: 0 1px 2px rgba(0,0,0,0.1); }
.btn { background: #007bff; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; }
.nav { margin-bottom: 20px; }
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/client.tsx"></script>
</body>
</html>
`)
})
export default app