feat(forum): public read endpoints (boards/threads/detail)
This commit is contained in:
parent
8cfa20d3aa
commit
9048a5a389
|
|
@ -0,0 +1,77 @@
|
|||
import Router from 'koa-router'
|
||||
import validate from '../middlewares/validator'
|
||||
import { db } from '..'
|
||||
import { fail } from '../common/utils'
|
||||
import { ERR_CODE, FORUM_BOARDS } from '../common/constants'
|
||||
|
||||
const forumRouter = new Router()
|
||||
forumRouter.prefix('/api/forum')
|
||||
|
||||
// 板块列表 + 帖数(公开)
|
||||
forumRouter.get('/boards', async ctx => {
|
||||
const grouped = await db.forumThread.groupBy({
|
||||
by: ['board'],
|
||||
where: { deleted: 0 },
|
||||
_count: { _all: true }
|
||||
})
|
||||
const countMap: Record<string, number> = {}
|
||||
grouped.forEach(g => { countMap[g.board] = g._count._all })
|
||||
ctx.body = FORUM_BOARDS.map(board => ({ board, count: countMap[board] ?? 0 }))
|
||||
})
|
||||
|
||||
// 帖子列表(公开,分页,按最新)
|
||||
forumRouter.get(
|
||||
'/threads',
|
||||
validate({
|
||||
query: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
board: { type: 'string' },
|
||||
page: { type: 'number' },
|
||||
size: { type: 'number' }
|
||||
}
|
||||
}
|
||||
}),
|
||||
async ctx => {
|
||||
const q = ctx.state.query as { board?: string, page?: number, size?: number }
|
||||
const page = q.page && q.page > 0 ? q.page : 1
|
||||
const size = q.size && q.size > 0 ? q.size : 20
|
||||
const where: { deleted: number, board?: string } = { deleted: 0 }
|
||||
if (q.board && FORUM_BOARDS.includes(q.board)) where.board = q.board
|
||||
const [list, total] = await Promise.all([
|
||||
db.forumThread.findMany({
|
||||
where,
|
||||
orderBy: { created_at: 'desc' },
|
||||
skip: (page - 1) * size,
|
||||
take: size,
|
||||
select: {
|
||||
id: true, board: true, title: true, author_name: true,
|
||||
like_count: true, reply_count: true, created_at: true
|
||||
}
|
||||
}),
|
||||
db.forumThread.count({ where })
|
||||
])
|
||||
ctx.body = { list, total }
|
||||
}
|
||||
)
|
||||
|
||||
// 帖子详情 + 回帖(公开)
|
||||
forumRouter.get('/threads/:id', async ctx => {
|
||||
const id = Number(ctx.params.id)
|
||||
if (!Number.isInteger(id)) {
|
||||
ctx.throw(422, fail(ERR_CODE.INVALID_REQ_DATA, '参数错误'))
|
||||
return
|
||||
}
|
||||
const thread = await db.forumThread.findFirst({ where: { id, deleted: 0 } })
|
||||
if (!thread) {
|
||||
ctx.throw(422, fail(ERR_CODE.UNKNOWN_ERR, '帖子不存在'))
|
||||
return
|
||||
}
|
||||
const replies = await db.forumReply.findMany({
|
||||
where: { thread_id: id, deleted: 0 },
|
||||
orderBy: { floor: 'asc' }
|
||||
})
|
||||
ctx.body = { thread, replies }
|
||||
})
|
||||
|
||||
export const forumRoutes = forumRouter.routes()
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import Koa from 'koa'
|
||||
import logger, { initLogger } from './common/logger'
|
||||
import errorHandler from './middlewares/error-handler'
|
||||
import queryTypeParser from './middlewares/query-typeparser'
|
||||
import { testRoutes } from './controllers/_test'
|
||||
import { adminRoutes } from './controllers/admin'
|
||||
import { userRoutes } from './controllers/user'
|
||||
import { createServer } from 'http'
|
||||
import cors from '@koa/cors'
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import { materialRoutes } from './controllers/material'
|
||||
import { configRoutes } from './controllers/config'
|
||||
import { templateRoutes } from './controllers/template'
|
||||
import { projectRoutes } from './controllers/project'
|
||||
import { predictRoutes } from './controllers/predict'
|
||||
import { auditRoutes } from './controllers/audit'
|
||||
import { forumRoutes } from './controllers/forum'
|
||||
import koaBody from 'koa-body'
|
||||
import path from 'path'
|
||||
import { scheduleJob } from 'node-schedule'
|
||||
// import { Server } from 'socket.io'
|
||||
|
||||
const app = new Koa()
|
||||
const port = process.env.IAPIP_SVR_PORT ?? 6060
|
||||
const server = createServer(app.callback())
|
||||
|
||||
const isDev = app.env == 'development'
|
||||
|
||||
initLogger(app.env)
|
||||
|
||||
app.use(errorHandler)
|
||||
if (isDev) app.use(cors({ credentials: true }))
|
||||
app.use(queryTypeParser())
|
||||
app.use(koaBody({
|
||||
multipart: true,
|
||||
formidable: {
|
||||
keepExtensions: true,
|
||||
uploadDir: path.join(__dirname, '/', '../../')
|
||||
}
|
||||
}))
|
||||
app.use(testRoutes)
|
||||
app.use(userRoutes)
|
||||
app.use(adminRoutes)
|
||||
app.use(configRoutes)
|
||||
app.use(predictRoutes)
|
||||
app.use(projectRoutes)
|
||||
app.use(templateRoutes)
|
||||
app.use(materialRoutes)
|
||||
app.use(auditRoutes)
|
||||
app.use(forumRoutes)
|
||||
|
||||
// export const ws = new Server(server, { cors: isDev ? { credentials: true } : undefined })
|
||||
// ws.use((_, next) => next(new Error()))
|
||||
|
||||
export const db = new PrismaClient({ log: isDev ? ['query', 'info', 'warn', 'error'] : ['error'] })
|
||||
process.on('SIGINT', async () => {
|
||||
await db.$disconnect()
|
||||
process.exit()
|
||||
})
|
||||
|
||||
scheduleJob('0 1 * * *', async () => {
|
||||
try {
|
||||
const deleteRows = await db.$executeRaw`DELETE FROM project WHERE status = 0 AND updated_at < adddate(current_date(), -30)`
|
||||
logger.info(`Deleted ${deleteRows} expired projects.`)
|
||||
} catch (err) {
|
||||
logger.error('Schedule Task Error', '->', err)
|
||||
}
|
||||
})
|
||||
|
||||
server.listen(port)
|
||||
logger.info(`Server started at ${port}.`)
|
||||
Loading…
Reference in New Issue