timetable-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 6b69da29adc6d62cc2220467e6ce064599292e8e
parent 154c3289366c2762fb80039d81dd840fdf2b2176
Author: Yuukin256 <Yuukin256@gmail.com>
Date:   Fri,  4 Nov 2022 00:14:15 +0900

classes api と classes/[classId]/standard api を実装

Diffstat:
Asrc/api/$api.ts | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/api/classes/_classId@number/standard.ts | 11+++++++++++
Asrc/api/classes/index.ts | 8++++++++
Asrc/pages/api/rest/v1/classes/[classId]/standard/api-types.ts | 3+++
Asrc/pages/api/rest/v1/classes/[classId]/standard/api-types.validator.ts | 282+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/pages/api/rest/v1/classes/[classId]/standard/index.ts | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/pages/api/rest/v1/classes/index.ts | 21+++++++++++++++++++++
7 files changed, 449 insertions(+), 0 deletions(-)

diff --git a/src/api/$api.ts b/src/api/$api.ts @@ -0,0 +1,41 @@ +import type { AspidaClient } from 'aspida' +import type { Methods as Methods0 } from './classes' +import type { Methods as Methods1 } from './classes/_classId@number/standard' + +const api = <T>({ baseURL, fetch }: AspidaClient<T>) => { + const prefix = (baseURL === undefined ? '' : baseURL).replace(/\/$/, '') + const PATH0 = '/classes' + const PATH1 = '/standard' + const GET = 'GET' + const POST = 'POST' + + return { + classes: { + _classId: (val1: number) => { + const prefix1 = `${PATH0}/${val1}` + + return { + standard: { + get: (option?: { config?: T | undefined } | undefined) => + fetch<Methods1['get']['resBody']>(prefix, `${prefix1}${PATH1}`, GET, option).json(), + $get: (option?: { config?: T | undefined } | undefined) => + fetch<Methods1['get']['resBody']>(prefix, `${prefix1}${PATH1}`, GET, option).json().then(r => r.body), + post: (option: { body: Methods1['post']['reqBody'], config?: T | undefined }) => + fetch<Methods1['post']['resBody']>(prefix, `${prefix1}${PATH1}`, POST, option).json(), + $post: (option: { body: Methods1['post']['reqBody'], config?: T | undefined }) => + fetch<Methods1['post']['resBody']>(prefix, `${prefix1}${PATH1}`, POST, option).json().then(r => r.body), + $path: () => `${prefix}${prefix1}${PATH1}` + } + } + }, + get: (option?: { config?: T | undefined } | undefined) => + fetch<Methods0['get']['resBody']>(prefix, PATH0, GET, option).json(), + $get: (option?: { config?: T | undefined } | undefined) => + fetch<Methods0['get']['resBody']>(prefix, PATH0, GET, option).json().then(r => r.body), + $path: () => `${prefix}${PATH0}` + } + } +} + +export type ApiInstance = ReturnType<typeof api> +export default api diff --git a/src/api/classes/_classId@number/standard.ts b/src/api/classes/_classId@number/standard.ts @@ -0,0 +1,11 @@ +import type { DefineMethods } from 'aspida'; +import type { PostApiRequestBody } from 'pages/api/rest/v1/classes/[classId]/standard/api-types'; +import type { StandardEn } from 'types'; + +export type Methods = DefineMethods<{ + get: { resBody: StandardEn }; + post: { + reqBody: PostApiRequestBody; + resBody: StandardEn; + }; +}>; diff --git a/src/api/classes/index.ts b/src/api/classes/index.ts @@ -0,0 +1,8 @@ +import type { SchoolClass } from '@prisma/client'; +import type { DefineMethods } from 'aspida'; + +export type Methods = DefineMethods<{ + get: { + resBody: SchoolClass[]; + }; +}>; diff --git a/src/pages/api/rest/v1/classes/[classId]/standard/api-types.ts b/src/pages/api/rest/v1/classes/[classId]/standard/api-types.ts @@ -0,0 +1,3 @@ +import type { StandardEn } from 'types'; + +export type PostApiRequestBody = StandardEn; diff --git a/src/pages/api/rest/v1/classes/[classId]/standard/api-types.validator.ts b/src/pages/api/rest/v1/classes/[classId]/standard/api-types.validator.ts @@ -0,0 +1,282 @@ +// @ts-nocheck +// eslint-disable +// This file is generated by create-validator-ts +import Ajv from 'ajv'; +import * as apiTypes from './api-types'; + +export const SCHEMA = { + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/PostApiRequestBody", + "definitions": { + "PostApiRequestBody": { + "$ref": "#/definitions/StandardEn" + }, + "StandardEn": { + "type": "object", + "properties": { + "Mon1": { + "type": "array", + "items": { + "type": "string" + } + }, + "Mon2": { + "type": "array", + "items": { + "type": "string" + } + }, + "Mon3": { + "type": "array", + "items": { + "type": "string" + } + }, + "Mon4": { + "type": "array", + "items": { + "type": "string" + } + }, + "Mon5": { + "type": "array", + "items": { + "type": "string" + } + }, + "Mon6": { + "type": "array", + "items": { + "type": "string" + } + }, + "Mon7": { + "type": "array", + "items": { + "type": "string" + } + }, + "Tue1": { + "type": "array", + "items": { + "type": "string" + } + }, + "Tue2": { + "type": "array", + "items": { + "type": "string" + } + }, + "Tue3": { + "type": "array", + "items": { + "type": "string" + } + }, + "Tue4": { + "type": "array", + "items": { + "type": "string" + } + }, + "Tue5": { + "type": "array", + "items": { + "type": "string" + } + }, + "Tue6": { + "type": "array", + "items": { + "type": "string" + } + }, + "Tue7": { + "type": "array", + "items": { + "type": "string" + } + }, + "Wed1": { + "type": "array", + "items": { + "type": "string" + } + }, + "Wed2": { + "type": "array", + "items": { + "type": "string" + } + }, + "Wed3": { + "type": "array", + "items": { + "type": "string" + } + }, + "Wed4": { + "type": "array", + "items": { + "type": "string" + } + }, + "Wed5": { + "type": "array", + "items": { + "type": "string" + } + }, + "Wed6": { + "type": "array", + "items": { + "type": "string" + } + }, + "Wed7": { + "type": "array", + "items": { + "type": "string" + } + }, + "Thu1": { + "type": "array", + "items": { + "type": "string" + } + }, + "Thu2": { + "type": "array", + "items": { + "type": "string" + } + }, + "Thu3": { + "type": "array", + "items": { + "type": "string" + } + }, + "Thu4": { + "type": "array", + "items": { + "type": "string" + } + }, + "Thu5": { + "type": "array", + "items": { + "type": "string" + } + }, + "Thu6": { + "type": "array", + "items": { + "type": "string" + } + }, + "Fri1": { + "type": "array", + "items": { + "type": "string" + } + }, + "Fri2": { + "type": "array", + "items": { + "type": "string" + } + }, + "Fri3": { + "type": "array", + "items": { + "type": "string" + } + }, + "Fri4": { + "type": "array", + "items": { + "type": "string" + } + }, + "Fri5": { + "type": "array", + "items": { + "type": "string" + } + }, + "Fri6": { + "type": "array", + "items": { + "type": "string" + } + }, + "Fri7": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "Mon1", + "Mon2", + "Mon3", + "Mon4", + "Mon5", + "Mon6", + "Mon7", + "Tue1", + "Tue2", + "Tue3", + "Tue4", + "Tue5", + "Tue6", + "Tue7", + "Wed1", + "Wed2", + "Wed3", + "Wed4", + "Wed5", + "Wed6", + "Wed7", + "Thu1", + "Thu2", + "Thu3", + "Thu4", + "Thu5", + "Thu6", + "Fri1", + "Fri2", + "Fri3", + "Fri4", + "Fri5", + "Fri6", + "Fri7" + ], + "additionalProperties": false + } + } +}; +const ajv = new Ajv({ removeAdditional: true }).addSchema(SCHEMA, "SCHEMA"); +export function validatePostApiRequestBody(payload: unknown): apiTypes.PostApiRequestBody { + /** Schema is defined in {@link SCHEMA.definitions.PostApiRequestBody } **/ + const validator = ajv.getSchema("SCHEMA#/definitions/PostApiRequestBody"); + const valid = validator(payload); + if (!valid) { + const error = new Error('Invalid PostApiRequestBody: ' + ajv.errorsText(validator.errors, {dataVar: "PostApiRequestBody"})); + error.name = "ValidationError"; + throw error; + } + return payload; +} + +export function isPostApiRequestBody(payload: unknown): payload is apiTypes.PostApiRequestBody { + try { + validatePostApiRequestBody(payload); + return true; + } catch (error) { + return false; + } +} diff --git a/src/pages/api/rest/v1/classes/[classId]/standard/index.ts b/src/pages/api/rest/v1/classes/[classId]/standard/index.ts @@ -0,0 +1,83 @@ +import { List } from 'immutable'; + +import { ABSTRACT_LESSON_NAMES } from 'constant'; +import { prisma } from 'lib/prisma'; + +import { isPostApiRequestBody } from './api-types.validator'; + +import type { ClassUnit } from '@prisma/client'; +import type { NextApiHandler } from 'next'; +import type { StandardEn } from 'types'; + +/** 完全 or 不完全 な ClassUnit 配列 から Standard データを返す */ +const classUnitsToStandard = (classUnits: ClassUnit[]): StandardEn => { + const find = (to: string) => classUnits.find((v) => v.abstractLessonName === to)?.subject ?? []; + return Object.fromEntries(ABSTRACT_LESSON_NAMES.map((v) => [v, find(v)])) as StandardEn; +}; + +const handler: NextApiHandler = async (req, res) => { + if (typeof req.query['classId'] !== 'string' || isNaN(parseInt(req.query['classId']))) { + return res.status(400).send('Bad Request'); + } + + const classId = parseInt(req.query['classId']); + + switch (req.method) { + case 'HEAD': { + return res.status(200).send('OK'); + } + + case 'GET': { + const classUnits = await prisma.classUnit.findMany({ + where: { schoolClassId: { equals: classId } }, + }); + + const standard = classUnitsToStandard(classUnits); + + return res.status(200).json(standard); + } + + case 'POST': { + if (isPostApiRequestBody(req.body)) { + const newStandard = req.body; + const oldClassUnits = await prisma.classUnit.findMany({ where: { schoolClassId: classId } }); + + // 各コマについてループ + // DBに変更を投げた Promise の配列が返る + const promises = ABSTRACT_LESSON_NAMES.flatMap((abstractLessonName) => { + const oldClassUnit = oldClassUnits.find((cu) => cu.abstractLessonName === abstractLessonName); + const newSubject = newStandard[abstractLessonName]; + + // 変化があった項目のみ更新(or作成) + if (oldClassUnit && !List(oldClassUnit.subject).equals(List(newSubject))) { + return prisma.classUnit.upsert({ + where: { schoolClassId_abstractLessonName: { schoolClassId: classId, abstractLessonName } }, + create: { abstractLessonName, subject: newSubject, SchoolClass: { connect: { id: classId } } }, + update: { subject: { set: newSubject } }, + }); + } + + return []; + }); + + await Promise.all(promises); + + // 更新後の Standard を取得して返す + const standard = classUnitsToStandard( + await prisma.classUnit.findMany({ + where: { schoolClassId: { equals: classId } }, + }) + ); + return res.status(200).json(standard); + } else { + return res.status(400).send('Bad Request'); + } + } + + default: { + return res.status(405).send('Method Not Allowed'); + } + } +}; + +export default handler; diff --git a/src/pages/api/rest/v1/classes/index.ts b/src/pages/api/rest/v1/classes/index.ts @@ -0,0 +1,21 @@ +import { prisma } from 'lib/prisma'; + +import type { NextApiHandler } from 'next'; + +const handler: NextApiHandler = async (req, res) => { + switch (req.method) { + case 'HEAD': { + return res.status(200).send('OK'); + } + + case 'GET': { + return res.status(200).json(await prisma.schoolClass.findMany({ orderBy: [{ grade: 'asc' }, { class: 'asc' }] })); + } + + default: { + return res.status(405).send('Method Not Allowed'); + } + } +}; + +export default handler;