Browse Source

update server

main
parent
commit
d6142bca01
5 changed files with 198 additions and 21 deletions
  1. +4
    -0
      .prettierrc.json
  2. +11
    -0
      .vscode/settings.json
  3. +25
    -21
      src/api/index.ts
  4. +32
    -0
      src/api/types.ts
  5. +126
    -0
      src/api/user.ts

+ 4
- 0
.prettierrc.json View File

@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}

+ 11
- 0
.vscode/settings.json View File

@ -0,0 +1,11 @@
{
"editor.tabSize": 2,
"[vue]": {
"editor.defaultFormatter": "octref.vetur",
"editor.formatOnSave": true
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
}

+ 25
- 21
src/api/index.ts View File

@ -11,6 +11,7 @@ import fastifySwagger from 'fastify-swagger'
import { db, isDev } from '../shared'
import { searchRouter } from './search'
import { userRouter } from './user'
const apiRouter: FastifyPluginAsync = async (f) => {
f.register(fCookie)
@ -129,36 +130,39 @@ const apiRouter: FastifyPluginAsync = async (f) => {
f.register(searchRouter, {
prefix: '/search',
})
f.register(userRouter, {
prefix: '/user',
})
}
export default apiRouter
async function main() {
const app = fastify({
logger: {
prettyPrint: isDev,
serializers: {
req(req) {
if (!req.url) {
return { method: req.method }
}
if (require.main === module) {
async function main() {
const app = fastify({
logger: {
prettyPrint: isDev,
serializers: {
req(req) {
if (!req.url) {
return { method: req.method }
}
const [url = '', q] = req.url.split(/\?(.+)$/)
const query = q ? qs.parse(q) : undefined
const [url = '', q] = req.url.split(/\?(.+)$/)
const query = q ? qs.parse(q) : undefined
return { method: req.method, url, query }
return { method: req.method, url, query }
},
},
},
},
})
app.register(apiRouter, {
prefix: '/api',
})
})
return app.listen(process.env['PORT']!, '0.0.0.0')
}
app.register(apiRouter, {
prefix: '/api',
})
if (require.main === module) {
return app.listen(process.env['PORT']!, '0.0.0.0')
}
main()
}

+ 32
- 0
src/api/types.ts View File

@ -0,0 +1,32 @@
import S from 'jsonschema-definer'
export const sTypeLevel = S.string().enum('character', 'vocabulary')
export const sType = S.anyOf(sTypeLevel, S.string().enum('sentence'))
export const sLevelBrowserByType = S.shape({
whatToShow: S.list(S.string()),
})
const oLevelBrowser: Record<
typeof sTypeLevel.type,
typeof sLevelBrowserByType
> = {
character: sLevelBrowserByType,
vocabulary: sLevelBrowserByType,
}
export const sLevelBrowser = S.shape(oLevelBrowser)
const oUserLocalSettings = {
level: S.integer(),
levelMin: S.integer().optional(),
level_browser: sLevelBrowser.optional(),
}
const oUserSettings = {
...oUserLocalSettings,
identifier: S.string(),
}
export const sUserLocalSettings = S.shape(oUserLocalSettings)
export const sUserSettings = S.shape(oUserSettings)

+ 126
- 0
src/api/user.ts View File

@ -0,0 +1,126 @@
import { SQLQuery, sql } from '@databases/pg'
import { FastifyPluginAsync } from 'fastify'
import S from 'jsonschema-definer'
import { db } from '../shared'
import { sUserSettings } from './types'
export const userRouter: FastifyPluginAsync = async (f) => {
{
const sQuery = S.shape({
select: S.string(),
})
const sResponse = sUserSettings.partial()
f.get<{
Querystring: typeof sQuery.type
}>(
'/',
{
schema: {
operationId: 'userGetSettings',
querystring: sQuery.valueOf(),
response: {
200: sResponse.valueOf(),
},
},
},
async (req): Promise<typeof sResponse.type> => {
const select = req.query.select.split(
','
) as (keyof typeof sUserSettings.type)[]
const userId: string = req.session['userId']
if (!userId) {
throw { statusCode: 403 }
}
const selMap: Record<keyof typeof sUserSettings.type, SQLQuery> = {
identifier: sql`"identifier"`,
level: sql`"level"`,
levelMin: sql`"levelMin"`,
level_browser: sql`"level_browser"`,
}
const sel = select.map((s) => selMap[s]).filter((s) => s)
if (!sel.length) {
throw { statusCode: 400, message: 'not enough select' }
}
const [r] = await db.query(
sql`
SELECT ${sql.join(sel, ',')} FROM "user" WHERE "id" = ${userId}
`
)
if (!r) {
throw { statusCode: 403 }
}
return {
identifier: r.identifier || undefined,
level: r.level || undefined,
levelMin: r.levelMin || undefined,
level_browser: r.levelBrowser || undefined,
}
}
)
}
{
const sBody = sUserSettings.partial()
const sResult = S.shape({
result: S.string(),
})
f.patch<{
Body: Partial<typeof sUserSettings.type>
}>(
'/',
{
schema: {
operationId: 'userUpdateSettings',
body: sBody.valueOf(),
response: {
201: sResult.valueOf(),
},
},
},
async (req, reply): Promise<typeof sResult.type> => {
const userId: string = req.session['userId']
if (!userId) {
throw { statusCode: 403 }
}
if (!Object.keys(req.body).length) {
throw { statusCode: 400, message: 'not enough update' }
}
const uMap: Partial<Record<keyof typeof sUserSettings.type, SQLQuery>> =
{
level: sql`"level" = ${req.body.level}`,
levelMin: sql`"levelMin" = ${req.body.levelMin}`,
level_browser: sql`"level_browser" = ${req.body.level_browser}`,
}
await db.tx(async (db) => {
await db.query(sql`
UPDATE "user"
SET ${sql.join(
Object.keys(req.body).map((k) => (uMap as any)[k]),
','
)}
WHERE "id" = ${userId}
`)
})
reply.status(201)
return {
result: 'updated',
}
}
)
}
}

Loading…
Cancel
Save