commit 5672d63f2a164a472bd0cb98bb38c65e1a7bd61f
parent a883703070f3d62b6c922ac9af509590a0254757
Author: Yuukin256 <Yuukin256@gmail.com>
Date: Sat, 5 Nov 2022 01:20:59 +0900
APIを修正
Diffstat:
8 files changed, 178 insertions(+), 82 deletions(-)
diff --git a/.eslintrc.yml b/.eslintrc.yml
@@ -39,9 +39,11 @@ rules:
'@typescript-eslint/consistent-type-imports': error
'@typescript-eslint/no-unused-vars':
- - error
- - varsIgnorePattern: _
- ignoreRestSiblings: true
+ - error
+ - varsIgnorePattern: _
+ ignoreRestSiblings: true
+
+ unused-imports/no-unused-imports: warn
react/jsx-key:
diff --git a/src/api/classes/_classId@number/standard.ts b/src/api/classes/_classId@number/standard.ts
@@ -1,11 +1,14 @@
import type { DefineMethods } from 'aspida';
-import type { PostApiRequestBody } from 'pages/api/rest/v1/classes/[classId]/standard/api-types';
-import type { StandardEn } from 'types';
+import type {
+ GetApiResponseBody,
+ PostApiRequestBody,
+ PostApiResponseBody,
+} from 'pages/api/rest/v1/classes/[classId]/standard/api-types';
export type Methods = DefineMethods<{
- get: { resBody: StandardEn };
+ get: { resBody: GetApiResponseBody };
post: {
reqBody: PostApiRequestBody;
- resBody: StandardEn;
+ resBody: PostApiResponseBody;
};
}>;
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
@@ -1,3 +1,5 @@
import type { StandardEn } from 'types';
+export type GetApiResponseBody = StandardEn;
export type PostApiRequestBody = StandardEn;
+export type PostApiResponseBody = 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
@@ -6,9 +6,8 @@ import * as apiTypes from './api-types';
export const SCHEMA = {
"$schema": "http://json-schema.org/draft-07/schema#",
- "$ref": "#/definitions/PostApiRequestBody",
"definitions": {
- "PostApiRequestBody": {
+ "GetApiResponseBody": {
"$ref": "#/definitions/StandardEn"
},
"StandardEn": {
@@ -256,10 +255,37 @@ export const SCHEMA = {
"Fri7"
],
"additionalProperties": false
+ },
+ "PostApiRequestBody": {
+ "$ref": "#/definitions/StandardEn"
+ },
+ "PostApiResponseBody": {
+ "$ref": "#/definitions/StandardEn"
}
}
};
const ajv = new Ajv({ removeAdditional: true }).addSchema(SCHEMA, "SCHEMA");
+export function validateGetApiResponseBody(payload: unknown): apiTypes.GetApiResponseBody {
+ /** Schema is defined in {@link SCHEMA.definitions.GetApiResponseBody } **/
+ const validator = ajv.getSchema("SCHEMA#/definitions/GetApiResponseBody");
+ const valid = validator(payload);
+ if (!valid) {
+ const error = new Error('Invalid GetApiResponseBody: ' + ajv.errorsText(validator.errors, {dataVar: "GetApiResponseBody"}));
+ error.name = "ValidationError";
+ throw error;
+ }
+ return payload;
+}
+
+export function isGetApiResponseBody(payload: unknown): payload is apiTypes.GetApiResponseBody {
+ try {
+ validateGetApiResponseBody(payload);
+ return true;
+ } catch (error) {
+ return false;
+ }
+}
+
export function validatePostApiRequestBody(payload: unknown): apiTypes.PostApiRequestBody {
/** Schema is defined in {@link SCHEMA.definitions.PostApiRequestBody } **/
const validator = ajv.getSchema("SCHEMA#/definitions/PostApiRequestBody");
@@ -280,3 +306,24 @@ export function isPostApiRequestBody(payload: unknown): payload is apiTypes.Post
return false;
}
}
+
+export function validatePostApiResponseBody(payload: unknown): apiTypes.PostApiResponseBody {
+ /** Schema is defined in {@link SCHEMA.definitions.PostApiResponseBody } **/
+ const validator = ajv.getSchema("SCHEMA#/definitions/PostApiResponseBody");
+ const valid = validator(payload);
+ if (!valid) {
+ const error = new Error('Invalid PostApiResponseBody: ' + ajv.errorsText(validator.errors, {dataVar: "PostApiResponseBody"}));
+ error.name = "ValidationError";
+ throw error;
+ }
+ return payload;
+}
+
+export function isPostApiResponseBody(payload: unknown): payload is apiTypes.PostApiResponseBody {
+ try {
+ validatePostApiResponseBody(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
@@ -1,82 +1,52 @@
-import { List } from 'immutable';
-
-import { ABSTRACT_LESSON_NAMES } from 'constant';
import { prisma } from 'lib/prisma';
-import { isPostApiRequestBody } from './api-types.validator';
+import { isPostApiRequestBody, validateGetApiResponseBody, validatePostApiResponseBody } 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');
+ try {
+ if (typeof req.query['classId'] !== 'string' || isNaN(parseInt(req.query['classId']))) {
+ return res.status(400).send('Bad Request');
}
- case 'GET': {
- const classUnits = await prisma.classUnit.findMany({
- where: { schoolClassId: { equals: classId } },
- });
+ const classId = parseInt(req.query['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 } },
- });
- }
+ switch (req.method) {
+ case 'HEAD': {
+ return res.status(200).send('OK');
+ }
- return [];
- });
+ case 'GET': {
+ const { classId: _, ...standard } = await prisma.standard.findUniqueOrThrow({ where: { classId } });
- await Promise.all(promises);
+ return res.status(200).json(validateGetApiResponseBody(standard));
+ }
- // 更新後の 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');
+ case 'POST': {
+ if (isPostApiRequestBody(req.body)) {
+ const newStandard = req.body;
+
+ // DBを更新
+ const { classId: _, ...updated } = await prisma.standard.upsert({
+ where: { classId },
+ create: { classId, ...newStandard },
+ update: { ...newStandard },
+ });
+
+ // 更新後の Standard を返す
+ return res.status(200).json(validatePostApiResponseBody(updated));
+ } else {
+ return res.status(400).send('Bad Request');
+ }
}
- }
- default: {
- return res.status(405).send('Method Not Allowed');
+ default: {
+ return res.status(405).send('Method Not Allowed');
+ }
}
+ } catch {
+ return res.status(500).send('Internal Server Error');
}
};
diff --git a/src/pages/api/rest/v1/classes/api-types.ts b/src/pages/api/rest/v1/classes/api-types.ts
@@ -0,0 +1,3 @@
+import type { SchoolClass } from '@prisma/client';
+
+export type GetApiResponseBody = SchoolClass[];
diff --git a/src/pages/api/rest/v1/classes/api-types.validator.ts b/src/pages/api/rest/v1/classes/api-types.validator.ts
@@ -0,0 +1,60 @@
+// @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/GetApiResponseBody",
+ "definitions": {
+ "GetApiResponseBody": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/SchoolClass"
+ }
+ },
+ "SchoolClass": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "number"
+ },
+ "class": {
+ "type": "number"
+ },
+ "grade": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "id",
+ "class",
+ "grade"
+ ],
+ "additionalProperties": false,
+ "description": "Model SchoolClass"
+ }
+ }
+};
+const ajv = new Ajv({ removeAdditional: true }).addSchema(SCHEMA, "SCHEMA");
+export function validateGetApiResponseBody(payload: unknown): apiTypes.GetApiResponseBody {
+ /** Schema is defined in {@link SCHEMA.definitions.GetApiResponseBody } **/
+ const validator = ajv.getSchema("SCHEMA#/definitions/GetApiResponseBody");
+ const valid = validator(payload);
+ if (!valid) {
+ const error = new Error('Invalid GetApiResponseBody: ' + ajv.errorsText(validator.errors, {dataVar: "GetApiResponseBody"}));
+ error.name = "ValidationError";
+ throw error;
+ }
+ return payload;
+}
+
+export function isGetApiResponseBody(payload: unknown): payload is apiTypes.GetApiResponseBody {
+ try {
+ validateGetApiResponseBody(payload);
+ return true;
+ } catch (error) {
+ return false;
+ }
+}
diff --git a/src/pages/api/rest/v1/classes/index.ts b/src/pages/api/rest/v1/classes/index.ts
@@ -1,20 +1,29 @@
import { prisma } from 'lib/prisma';
+import { validateGetApiResponseBody } from './api-types.validator';
+
import type { NextApiHandler } from 'next';
+// TODO: next-connect に置き換え
const handler: NextApiHandler = async (req, res) => {
- switch (req.method) {
- case 'HEAD': {
- return res.status(200).send('OK');
- }
+ try {
+ 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' }] }));
- }
+ case 'GET': {
+ const classes = await prisma.schoolClass.findMany({ orderBy: [{ grade: 'asc' }, { class: 'asc' }] });
+
+ return res.status(200).json(validateGetApiResponseBody(classes));
+ }
- default: {
- return res.status(405).send('Method Not Allowed');
+ default: {
+ return res.status(405).send('Method Not Allowed');
+ }
}
+ } catch {
+ return res.status(500).send('Internal Server Error');
}
};