nose-bbs
This commit is contained in:
parent
f20e280b19
commit
5d7f27e296
|
|
@ -0,0 +1 @@
|
|||
../../CLAUDE.md
|
||||
34
packages/nose-bbs/.gitignore
vendored
Normal file
34
packages/nose-bbs/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# dependencies (bun install)
|
||||
node_modules
|
||||
|
||||
# output
|
||||
out
|
||||
dist
|
||||
*.tgz
|
||||
|
||||
# code coverage
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# logs
|
||||
logs
|
||||
_.log
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# caches
|
||||
.eslintcache
|
||||
.cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
6
packages/nose-bbs/README.md
Normal file
6
packages/nose-bbs/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# nose-bbs
|
||||
|
||||
## Quickstart
|
||||
|
||||
bun install
|
||||
bun dev
|
||||
20
packages/nose-bbs/package.json
Normal file
20
packages/nose-bbs/package.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "nose-bbs",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "bun subdomain:dev",
|
||||
"subdomain:start": "bun run src/server.tsx",
|
||||
"subdomain:dev": "bun run --hot src/server.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"hono": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
99
packages/nose-bbs/src/server.tsx
Normal file
99
packages/nose-bbs/src/server.tsx
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { Hono } from "hono"
|
||||
import { Database } from "bun:sqlite"
|
||||
import { join } from "path"
|
||||
|
||||
export const DATA_DIR = process.env.DATA_DIR || "."
|
||||
const api = new Hono()
|
||||
const db = new Database(join(DATA_DIR, "bbs.sqlite"))
|
||||
|
||||
api.use("*", async (c, next) => {
|
||||
const method = c.req.method
|
||||
const url = c.req.url
|
||||
|
||||
let body = ""
|
||||
if (method === "POST" || method === "PUT" || method === "PATCH") {
|
||||
try {
|
||||
const clonedRequest = c.req.raw.clone()
|
||||
body = await clonedRequest.text()
|
||||
console.log(`${method} ${url} - Body: ${body}`)
|
||||
} catch (error) {
|
||||
console.log(`${method} ${url} - Body: [unable to parse]`)
|
||||
}
|
||||
} else {
|
||||
console.log(`${method} ${url}`)
|
||||
}
|
||||
|
||||
await next()
|
||||
})
|
||||
|
||||
db.run(`
|
||||
CREATE TABLE IF NOT EXISTS topics (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
author TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS replies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
topic_id INTEGER NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
author TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
`)
|
||||
|
||||
api.get("/topics", async c => {
|
||||
const topics = db.query("SELECT * FROM topics ORDER BY id DESC").all()
|
||||
return c.json(topics)
|
||||
})
|
||||
|
||||
api.post("/topics", async c => {
|
||||
const { title, content, author } = await c.req.json()
|
||||
|
||||
if (!title || !content || !author) {
|
||||
return c.json({ error: "title, content and author are required" }, 400)
|
||||
}
|
||||
|
||||
const topic = db.query(`
|
||||
INSERT INTO
|
||||
topics(title, content, author, created_at)
|
||||
VALUES($title, $content, $author, $created_at)
|
||||
`).run({ $title: title, $content: content, $author: author, $created_at: new Date().toISOString() })
|
||||
return c.json({ id: topic.lastInsertRowid, ok: true })
|
||||
})
|
||||
|
||||
api.get("/topics/:id", async c => {
|
||||
const id = c.req.param("id")
|
||||
if (!id) {
|
||||
return c.json({ error: "id is required" }, 400)
|
||||
}
|
||||
|
||||
let topic = db.query("SELECT * FROM topics WHERE id = $id").get({ $id: id })
|
||||
if (!topic) {
|
||||
return c.json({ error: "topic not found" }, 404)
|
||||
}
|
||||
const replies = db.query("SELECT * FROM replies WHERE topic_id = $id").all({ $id: id })
|
||||
; (topic as any).replies = replies
|
||||
|
||||
return c.json(topic)
|
||||
})
|
||||
|
||||
api.post("/topics/:id/reply", async c => {
|
||||
const id = c.req.param("id")
|
||||
if (!id) {
|
||||
return c.json({ error: "id is required" }, 400)
|
||||
}
|
||||
const { content, author } = await c.req.json()
|
||||
const reply = db.query(`
|
||||
INSERT INTO replies (topic_id, content, author, created_at)
|
||||
VALUES ($topic_id, $content, $author, $created_at)
|
||||
`).run({ $topic_id: id, $content: content, $author: author, $created_at: new Date().toISOString() })
|
||||
return c.json({ id: reply.lastInsertRowid, ok: true })
|
||||
})
|
||||
|
||||
export default {
|
||||
port: process.env.PORT || 3001,
|
||||
fetch: api.fetch,
|
||||
}
|
||||
29
packages/nose-bbs/tsconfig.json
Normal file
29
packages/nose-bbs/tsconfig.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "Preserve",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user