From 587136a82d21fcfe654efbcb342cd3581a478f8f Mon Sep 17 00:00:00 2001 From: Xeltica Date: Mon, 13 Sep 2021 22:39:14 +0900 Subject: [PATCH] wip --- src/backend/controllers/UserSetting.ts | 23 ++++++++++++++++++++ src/backend/controllers/session.ts | 20 ++++++++++++++++- src/backend/server.ts | 4 +++- src/frontend/components/SessionDataPage.tsx | 15 ++++++++++++- src/frontend/components/SettingPage.tsx | 24 +++++++++++++++++---- src/frontend/const.ts | 3 +++ src/frontend/const.tsx | 1 - src/frontend/init.tsx | 3 ++- src/frontend/misc/api.ts | 0 src/frontend/pages/index.tsx | 3 ++- src/frontend/services/session.ts | 8 +++---- 11 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 src/backend/controllers/UserSetting.ts create mode 100644 src/frontend/const.ts delete mode 100644 src/frontend/const.tsx create mode 100644 src/frontend/misc/api.ts diff --git a/src/backend/controllers/UserSetting.ts b/src/backend/controllers/UserSetting.ts new file mode 100644 index 0000000..9315383 --- /dev/null +++ b/src/backend/controllers/UserSetting.ts @@ -0,0 +1,23 @@ +import { IsIn, IsOptional } from 'class-validator'; +import { AlertMode, alertModes } from '../../common/types/alert-mode'; +import { visibilities, Visibility } from '../../common/types/visibility'; + + +export class UserSetting { + @IsIn(alertModes) + @IsOptional() + alertMode?: AlertMode; + + @IsIn(visibilities) + @IsOptional() + visibility?: Visibility; + + @IsOptional() + localOnly?: boolean; + + @IsOptional() + remoteFollowersOnly?: boolean; + + @IsOptional() + template?: string; +} diff --git a/src/backend/controllers/session.ts b/src/backend/controllers/session.ts index fd24134..c58a1c0 100644 --- a/src/backend/controllers/session.ts +++ b/src/backend/controllers/session.ts @@ -3,9 +3,13 @@ * @author Xeltica */ -import { CurrentUser, Get, JsonController } from 'routing-controllers'; +import { IsEnum } from 'class-validator'; +import { Body, CurrentUser, Get, HttpCode, JsonController, OnUndefined, Post, Put } from 'routing-controllers'; +import { DeepPartial } from 'typeorm'; import { getScores } from '../functions/get-scores'; +import { updateUser } from '../functions/users'; import { User } from '../models/entities/user'; +import { UserSetting } from './UserSetting'; @JsonController('/session') export class SessionController { @@ -17,4 +21,18 @@ export class SessionController { async getScore(@CurrentUser({ required: true }) user: User) { return getScores(user); } + + @OnUndefined(204) + @Put() async updateSetting(@CurrentUser({ required: true }) user: User, @Body() setting: UserSetting) { + const s: DeepPartial = {}; + if (setting.alertMode) s.alertMode = setting.alertMode; + if (setting.visibility) s.visibility = setting.visibility; + if (setting.localOnly) s.localOnly = setting.localOnly; + if (setting.remoteFollowersOnly) s.remoteFollowersOnly = setting.remoteFollowersOnly; + if (setting.template) s.template = setting.template; + if (Object.keys(s).length === 0) return; + await updateUser(user.username, user.host, s); + } } + + diff --git a/src/backend/server.ts b/src/backend/server.ts index e1a42e0..ecd8000 100644 --- a/src/backend/server.ts +++ b/src/backend/server.ts @@ -23,7 +23,8 @@ export default (): void => { useKoaServer(app, { controllers: [__dirname + '/controllers/**/*{.ts,.js}'], routePrefix: '/api/v1', - defaultErrorHandler: false, + classTransformer: true, + validation: true, currentUserChecker: async ({ request }: Action) => { const { authorization } = request.header; if (!authorization || !authorization.startsWith('Bearer ')) return null; @@ -35,6 +36,7 @@ export default (): void => { }); app.use(router.routes()); + app.use(router.allowedMethods()); app.keys = ['人類', 'ミス廃化', '計画', 'ここに極まれり', 'フッフッフ...']; diff --git a/src/frontend/components/SessionDataPage.tsx b/src/frontend/components/SessionDataPage.tsx index 458805b..32ff76d 100644 --- a/src/frontend/components/SessionDataPage.tsx +++ b/src/frontend/components/SessionDataPage.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import React, { useEffect } from 'react'; +import { LOCALSTORAGE_KEY_TOKEN } from '../const'; import { useGetScoreQuery, useGetSessionQuery } from '../services/session'; import { Skeleton } from './Skeleton'; @@ -6,6 +7,18 @@ export const SessionDataPage: React.VFC = () => { const session = useGetSessionQuery(undefined); const score = useGetScoreQuery(undefined); + /** + * Session APIのエラーハンドリング + * このAPIがエラーを返した = トークンが無効 なのでトークンを削除してログアウトする + */ + useEffect(() => { + if (session.error) { + console.error(session.error); + localStorage.removeItem(LOCALSTORAGE_KEY_TOKEN); + location.reload(); + } + }, [session.error]); + return session.isLoading || score.isLoading ? (
diff --git a/src/frontend/components/SettingPage.tsx b/src/frontend/components/SettingPage.tsx index 872fa0d..5b283b8 100644 --- a/src/frontend/components/SettingPage.tsx +++ b/src/frontend/components/SettingPage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useReducer, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'; import { AlertMode } from '../../common/types/alert-mode'; import { IUser } from '../../common/types/user'; import { Visibility } from '../../common/types/visibility'; @@ -6,6 +6,7 @@ import { useGetSessionQuery } from '../services/session'; import { defaultTemplate } from '../../common/default-template'; import { Card } from './Card'; import { Theme } from '../misc/theme'; +import { API_ENDPOINT, LOCALSTORAGE_KEY_TOKEN } from '../const'; type SettingDraftType = Pick { const [currentTheme, setCurrentTheme] = useState('light'); const [currentLang, setCurrentLang] = useState('ja-JP'); + const updateSetting = useCallback(() => { + fetch(`${API_ENDPOINT}session`, { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${localStorage[LOCALSTORAGE_KEY_TOKEN]}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(draft), + }) + .then(() => alert('設定を保存しました。')) + .catch(e => { + alert(e.message); + }); + }, [draft]); + useEffect(() => { if (data) { dispatchDraft({ @@ -60,13 +76,13 @@ export const SettingPage: React.VFC = () => { template: data.template, }); } - }, [session.data]); + }, [data]); const saveButton = useMemo(() => ( - - ), []); + ), [updateSetting]); return session.isLoading || !data ? (
diff --git a/src/frontend/const.ts b/src/frontend/const.ts new file mode 100644 index 0000000..d9d5eab --- /dev/null +++ b/src/frontend/const.ts @@ -0,0 +1,3 @@ +export const LOCALSTORAGE_KEY_TOKEN = 'token'; + +export const API_ENDPOINT = `//${location.host}/api/v1/`; diff --git a/src/frontend/const.tsx b/src/frontend/const.tsx deleted file mode 100644 index 286f411..0000000 --- a/src/frontend/const.tsx +++ /dev/null @@ -1 +0,0 @@ -export const apiEndpoint = `//${location.host}/api/v1/`; diff --git a/src/frontend/init.tsx b/src/frontend/init.tsx index 7a06669..2e2aff9 100644 --- a/src/frontend/init.tsx +++ b/src/frontend/init.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { App } from './App'; +import { LOCALSTORAGE_KEY_TOKEN } from './const'; document.body.classList.add('dark'); @@ -11,7 +12,7 @@ const token = document.cookie ?.split('=')[1]; if (token) { - localStorage['token'] = token; + localStorage[LOCALSTORAGE_KEY_TOKEN] = token; // 今の所はcookieをトークン以外に使用しないため全消去する // もしcookieの用途が増えるのであればここを良い感じに書き直す必要がある document.cookie = ''; diff --git a/src/frontend/misc/api.ts b/src/frontend/misc/api.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/frontend/pages/index.tsx b/src/frontend/pages/index.tsx index 080f1e6..4745790 100644 --- a/src/frontend/pages/index.tsx +++ b/src/frontend/pages/index.tsx @@ -1,10 +1,11 @@ import React from 'react'; +import { LOCALSTORAGE_KEY_TOKEN } from '../const'; import { IndexSessionPage } from './index.session'; import { IndexWelcomePage } from './index.welcome'; export const IndexPage: React.VFC = () => { - const token = localStorage.getItem('token'); + const token = localStorage[LOCALSTORAGE_KEY_TOKEN]; return token ? : ; }; diff --git a/src/frontend/services/session.ts b/src/frontend/services/session.ts index 5fefcc4..950163f 100644 --- a/src/frontend/services/session.ts +++ b/src/frontend/services/session.ts @@ -1,17 +1,17 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; -import { apiEndpoint } from '../const'; +import { API_ENDPOINT, LOCALSTORAGE_KEY_TOKEN } from '../const'; import { IUser } from '../../common/types/user'; import { Score } from '../../common/types/score'; export const sessionApi = createApi({ reducerPath: 'session', - baseQuery: fetchBaseQuery({ baseUrl: apiEndpoint + 'session' }), + baseQuery: fetchBaseQuery({ baseUrl: API_ENDPOINT + 'session' }), endpoints: (builder) => ({ getSession: builder.query({ query: () => ({ url: '/', headers: { - 'Authorization': `Bearer ${localStorage['token']}`, + 'Authorization': `Bearer ${localStorage[LOCALSTORAGE_KEY_TOKEN]}`, } }) }), @@ -19,7 +19,7 @@ export const sessionApi = createApi({ query: () => ({ url: '/score', headers: { - 'Authorization': `Bearer ${localStorage['token']}`, + 'Authorization': `Bearer ${localStorage[LOCALSTORAGE_KEY_TOKEN]}`, } }) }),