feat: migrate quest api to trpc

This commit is contained in:
Justin Lin committed 2025-04-16 16:24:29 +08:00
1 parent 55a41dd3b5
commit ac92950177
14 files changed
+55 -71

No files matched your search

+2 -2
View File
@@ -2,8 +2,8 @@
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
import { ref, defineProps, onMounted } from 'vue' import { ref, defineProps, onMounted } from 'vue'
import { getQuest } from '../utils/api'
import MarkdownText from './MarkdownText.vue' import MarkdownText from './MarkdownText.vue'
import { trpc } from '../utils/trpc'
const props = defineProps({ const props = defineProps({
questTitle: { questTitle: {
@@ -49,7 +49,7 @@ const handleColour = () => {
} }
} }
onMounted(async () => { onMounted(async () => {
const quest = await getQuest(questId) const quest = await trpc.quests.getQuestDetail.query(questId)
questInstruction.value = quest.instruction questInstruction.value = quest.instruction
handleColour() handleColour()
}) })
+2 -2
View File
@@ -2,7 +2,7 @@
import { ref, onMounted, watch, computed } from 'vue' import { ref, onMounted, watch, computed } from 'vue'
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
import { DAG } from '@linux-odyssey/utils' import { DAG } from '@linux-odyssey/utils'
import { getQuests } from '../utils/api' import { trpc } from '../utils/trpc'
import useUserProfile from '../store/userProfile' import useUserProfile from '../store/userProfile'
import QuestIntro from './QuestIntro.vue' import QuestIntro from './QuestIntro.vue'
@@ -64,7 +64,7 @@ async function computeGraphData() {
if (!store.progress) return if (!store.progress) return
try { try {
const quests = await getQuests() const quests = await trpc.quests.getQuests.query()
const dag = new DAG(quests) const dag = new DAG(quests)
const nodesValues = dag.getNodes() const nodesValues = dag.getNodes()
+5 -4
View File
@@ -1,10 +1,11 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { FileGraph, FileObject } from '@linux-odyssey/file-graph' import { FileGraph, FileObject } from '@linux-odyssey/file-graph'
import { IQuest } from '@linux-odyssey/models' import { QuestDetailResponse } from '@linux-odyssey/constants'
import { createSession, getActiveSession, getQuest } from '../utils/api' import { createSession, getActiveSession } from '../utils/api'
import Socket from '../utils/socket' import Socket from '../utils/socket'
import SocketTerminal from '../utils/terminal' import SocketTerminal from '../utils/terminal'
import { Session, StageResponse } from '../types' import { Session, StageResponse } from '../types'
import { trpc } from '../utils/trpc'
const socket = new Socket() const socket = new Socket()
const term = new SocketTerminal(40, 80) const term = new SocketTerminal(40, 80)
@@ -13,7 +14,7 @@ let hasSetup = false
interface Store { interface Store {
session: Session | null session: Session | null
questId: string questId: string
quest: IQuest | null quest: QuestDetailResponse | null
} }
const useSession = defineStore('session', { const useSession = defineStore('session', {
@@ -24,7 +25,7 @@ const useSession = defineStore('session', {
}), }),
actions: { actions: {
async setQuest(questId: string) { async setQuest(questId: string) {
this.quest = await getQuest(questId) this.quest = await trpc.quests.getQuestDetail.query(questId)
this.questId = questId this.questId = questId
}, },
async setSession(session: Session) { async setSession(session: Session) {
-11
View File
@@ -1,5 +1,4 @@
import axios from 'axios' import axios from 'axios'
import { IQuest } from '@linux-odyssey/models'
import { GetSessionsRequest } from '@linux-odyssey/constants' import { GetSessionsRequest } from '@linux-odyssey/constants'
import { Session } from '../types' import { Session } from '../types'
import { UserProfile } from '../store/userProfile' import { UserProfile } from '../store/userProfile'
@@ -18,11 +17,6 @@ export async function getUserProfile(): Promise<UserProfile> {
return res.data return res.data
} }
export async function getQuest(id: string): Promise<IQuest> {
const res = await api.get<IQuest>(`/quests/${id}`)
return res.data
}
export async function createSession(questId: string): Promise<Session> { export async function createSession(questId: string): Promise<Session> {
const res = await api.post<Session>('/sessions', { questId }) const res = await api.post<Session>('/sessions', { questId })
return res.data return res.data
@@ -43,8 +37,3 @@ export async function getActiveSession(
}) })
return res.data return res.data
} }
export async function getQuests(): Promise<IQuest[]> {
const res = await api.get<IQuest[]>('/quests')
return res.data
}
+2
View File
@@ -1,5 +1,7 @@
import { z } from 'zod' import { z } from 'zod'
export * from './src/quest.js'
export const passwordPolicy = { export const passwordPolicy = {
minLength: 8, minLength: 8,
minNumbers: 1, minNumbers: 1,
+6
View File
@@ -0,0 +1,6 @@
export interface QuestDetailResponse {
_id: string
title: string
instruction: string
requirements: string[]
}
+3 -1
View File
@@ -2,8 +2,10 @@
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"module": "Node16",
"moduleResolution": "node16",
"outDir": "./dist" "outDir": "./dist"
}, },
"include": ["*.js", "*.ts"], "include": ["*.js", "*.ts", "src/**/*.ts"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]
} }
@@ -1,28 +0,0 @@
import { Quest } from '@linux-odyssey/models'
import { matchedData } from 'express-validator'
import type { Request, Response } from 'express'
import { asyncHandler } from '../../middleware/error.js'
export const getQuests = asyncHandler(async (req: Request, res: Response) => {
const quests = await Quest.find({})
res.json(
quests.map(({ title, _id, requirements }) => ({
_id,
title,
requirements,
}))
)
})
export const getQuestDetail = asyncHandler(
async (req: Request, res: Response) => {
const { questId } = matchedData(req)
const quest = await Quest.findById(questId)
if (!quest) {
res.status(404).json({ message: 'Quest not found.' })
return
}
const { _id, title, instruction, requirements } = quest
res.json({ _id, title, instruction, requirements })
}
)
-2
View File
@@ -1,7 +1,6 @@
import { Router } from 'express' import { Router } from 'express'
import passport from 'passport' import passport from 'passport'
import sessions from './sessionRouter.js' import sessions from './sessionRouter.js'
import quests from './questRouter.js'
import commands from './commandRouter.js' import commands from './commandRouter.js'
import authRouter from './authRouter.js' import authRouter from './authRouter.js'
import authRequired from '../../middleware/authRequired.js' import authRequired from '../../middleware/authRequired.js'
@@ -29,7 +28,6 @@ router.use(
router.use('/auth', authRouter) router.use('/auth', authRouter)
router.use('/sessions', authRequired, sessions) router.use('/sessions', authRequired, sessions)
router.use('/quests', quests)
router.use('/users', userRouter) router.use('/users', userRouter)
router.get('/survey', (req, res) => { router.get('/survey', (req, res) => {
-10
View File
@@ -1,10 +0,0 @@
import { Router } from 'express'
import * as questController from '../controllers/questController.js'
import { questIdValidator } from '../validators/questValidator.js'
const router = Router()
router.get('/', questController.getQuests)
router.get('/:questId', questIdValidator, questController.getQuestDetail)
export default router
@@ -1,7 +0,0 @@
import { check } from 'express-validator'
import { noError } from '../../middleware/validator.js'
export const checkQuestId = () =>
check('questId').notEmpty().isString().isLowercase().isLength({ max: 64 })
export const questIdValidator = [checkQuestId(), noError]
@@ -1,5 +1,4 @@
import { check } from 'express-validator' import { check } from 'express-validator'
import { checkQuestId } from './questValidator.js'
import { noError } from '../../middleware/validator.js' import { noError } from '../../middleware/validator.js'
const checkSessionStatus = () => const checkSessionStatus = () =>
@@ -8,7 +7,7 @@ const checkSessionStatus = () =>
export const checkSessionId = () => export const checkSessionId = () =>
check('sessionId').notEmpty().isString().isMongoId() check('sessionId').notEmpty().isString().isMongoId()
export const sessionListValidator = [checkQuestId(), checkSessionStatus()] export const sessionListValidator = [checkSessionStatus()]
export const sessionDetailValidator = [checkSessionId()] export const sessionDetailValidator = [checkSessionId()]
export const createSessionValidator = [checkQuestId(), noError] export const createSessionValidator = [noError]
export const activeSessionValidator = [checkQuestId(), noError] export const activeSessionValidator = [noError]
+2
View File
@@ -1,5 +1,6 @@
import { router, publicProcedure } from '../trpc.js' import { router, publicProcedure } from '../trpc.js'
import { linkRouter } from './linkRouter.js' import { linkRouter } from './linkRouter.js'
import { questRouter } from './questRouter.js'
export const appRouter = router({ export const appRouter = router({
userList: publicProcedure.query(async (opts) => { userList: publicProcedure.query(async (opts) => {
@@ -7,6 +8,7 @@ export const appRouter = router({
return opts.ctx return opts.ctx
}), }),
links: linkRouter, links: linkRouter,
quests: questRouter,
}) })
export type AppRouter = typeof appRouter export type AppRouter = typeof appRouter
+30
View File
@@ -0,0 +1,30 @@
import { TRPCError } from '@trpc/server'
import { Quest } from '@linux-odyssey/models'
import { QuestDetailResponse } from '@linux-odyssey/constants'
import { z } from 'zod'
import { router, publicProcedure } from '../trpc.js'
export const questRouter = router({
getQuests: publicProcedure.query(async () => {
const quests = await Quest.find({})
return quests.map(({ title, _id, requirements }) => ({
_id,
title,
requirements,
}))
}),
getQuestDetail: publicProcedure
.input(z.string())
.query(async (opts): Promise<QuestDetailResponse> => {
const id = opts.input
const quest = await Quest.findById(id)
if (!quest) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Quest not found.',
})
}
const { _id, title, instruction, requirements } = quest
return { _id, title, instruction, requirements }
}),
})