diff --git a/apps/snutt-webclient/src/App.tsx b/apps/snutt-webclient/src/App.tsx index 3b77434e..235ba549 100644 --- a/apps/snutt-webclient/src/App.tsx +++ b/apps/snutt-webclient/src/App.tsx @@ -25,11 +25,11 @@ import { implFeedbackSnuttApiRepository } from '@/infrastructures/implFeedbackSn import { getNotificationRepository } from '@/infrastructures/implNotificationSnuttApiRepository'; import { implSearchSnuttApiRepository } from '@/infrastructures/implSearchSnuttApiRepository'; import { implSemesterSnuttApiRepository } from '@/infrastructures/implSemesterSnuttApiRepository'; +import { implUserSnuttApiRepository } from '@/infrastructures/implUserSnuttApiRepository'; import { ErrorPage } from '@/pages/error'; import { Main } from '@/pages/main'; import { MyPage } from '@/pages/mypage'; import { getTimetableRepository } from '@/repositories/timetableRepository'; -import { getUserRepository } from '@/repositories/userRepository'; import { getAuthService } from '@/usecases/authService'; import { getColorService } from '@/usecases/colorService'; import { getErrorService } from '@/usecases/errorService'; @@ -166,17 +166,11 @@ const GlobalStyles = createGlobalStyle` `; const getUnauthorizedServices = (ENV: { API_BASE_URL: string; API_KEY: string }) => { - const httpClient = createFetchClient({ - baseURL: ENV.API_BASE_URL, - headers: { 'x-access-apikey': ENV.API_KEY }, - }); - const snuttApi = getSnuttApi(ENV); const authRepository = implAuthSnuttApiRepository({ snuttApi }); const feedbackRepository = implFeedbackSnuttApiRepository({ snuttApi }); - const userRepository = getUserRepository({ httpClient }); - const authService = getAuthService({ authRepository, userRepository }); + const authService = getAuthService({ authRepository }); const feedbackService = getFeedbackService({ feedbackRepository }); return { authService, feedbackService }; @@ -198,7 +192,7 @@ const getAuthorizedServices = ( const snuttApi = getSnuttApi(ENV); - const userRepository = getUserRepository({ httpClient }); + const userRepository = implUserSnuttApiRepository({ snuttApi }); const authRepository = implAuthSnuttApiRepository({ snuttApi }); const timetableRepository = getTimetableRepository({ httpClient }); const semesterRepository = implSemesterSnuttApiRepository({ snuttApi }); @@ -206,7 +200,7 @@ const getAuthorizedServices = ( const notificationRepository = getNotificationRepository({ snuttApi }); const colorRepository = implColorSnuttApiRepository({ snuttApi }); - const userService = getUserService({ repositories: [userRepository] }); + const userService = getUserService({ userRepository }); const colorService = getColorService({ colorRepository }); const notificationService = getNotificationService({ notificationRepository }); const searchService = getSearchService({ searchRepository }); @@ -215,7 +209,7 @@ const getAuthorizedServices = ( const timeMaskService = getTimeMaskService(); const hourMinuteService = getHourMinuteService(); const hourMinutePickerService = getHourMinutePickerService({ services: [hourMinuteService] }); - const authService = getAuthService({ authRepository, userRepository }); + const authService = getAuthService({ authRepository }); const semesterService = getSemesterService({ semesterRepository }); return { diff --git a/apps/snutt-webclient/src/components/layout/layout-profile/index.tsx b/apps/snutt-webclient/src/components/layout/layout-profile/index.tsx index 62b657e7..2a3e9b83 100644 --- a/apps/snutt-webclient/src/components/layout/layout-profile/index.tsx +++ b/apps/snutt-webclient/src/components/layout/layout-profile/index.tsx @@ -3,13 +3,13 @@ import { Link } from 'react-router-dom'; import styled from 'styled-components'; import { serviceContext } from '@/contexts/ServiceContext'; +import { useTokenAuthContext } from '@/contexts/TokenAuthContext'; import { useGuardContext } from '@/hooks/useGuardContext'; -import { queryKey } from '@/utils/query-key-factory'; export const LayoutProfile = () => { const { data: myInfo } = useMyInfo(); - const isTempUser = !myInfo?.local_id && !myInfo?.fb_name; + const isTempUser = myInfo && myInfo.type === 'success' && !myInfo.data.localId && !myInfo.data.facebookName; const isLoginButton = isTempUser; return isLoginButton ? ( @@ -18,17 +18,18 @@ export const LayoutProfile = () => { ) : ( - {myInfo?.local_id ?? myInfo?.fb_name}님 + {myInfo?.type === 'success' && `${myInfo.data.localId ?? myInfo.data.facebookName}님`} ); }; const useMyInfo = () => { const { userService } = useGuardContext(serviceContext); + const { token } = useTokenAuthContext(); return useQuery({ - queryKey: queryKey('user/info'), - queryFn: () => userService.getUserInfo(), + queryKey: ['UserService', 'getUserInfo', { token }] as const, + queryFn: ({ queryKey }) => userService.getUserInfo(queryKey[2]), }); }; diff --git a/apps/snutt-webclient/src/entities/user.ts b/apps/snutt-webclient/src/entities/user.ts index 76427123..4d82b323 100644 --- a/apps/snutt-webclient/src/entities/user.ts +++ b/apps/snutt-webclient/src/entities/user.ts @@ -1,8 +1,8 @@ export interface User { - email?: string; - fb_name: string | null; + email: string | null; + facebookName: string | null; isAdmin: boolean; - local_id: string | null; + localId: string | null; notificationCheckedAt: string; regDate: string; } diff --git a/apps/snutt-webclient/src/infrastructures/implAuthSnuttApiRepository.ts b/apps/snutt-webclient/src/infrastructures/implAuthSnuttApiRepository.ts index abcf65e3..68dc07f8 100644 --- a/apps/snutt-webclient/src/infrastructures/implAuthSnuttApiRepository.ts +++ b/apps/snutt-webclient/src/infrastructures/implAuthSnuttApiRepository.ts @@ -14,7 +14,12 @@ export const implAuthSnuttApiRepository = ({ else return { type: 'error', errcode: data.errcode }; }, signInWithFacebook: async (body) => { - const { status, data } = await snuttApi['POST /auth/login_fb']({ body }); + const { status, data } = await snuttApi['POST /auth/login_fb']({ + body: { + fb_id: body.facebookId, + fb_token: body.facebookToken, + }, + }); if (status === 200) return { type: 'success', data }; else return { type: 'error', errcode: data.errcode }; }, @@ -29,24 +34,45 @@ export const implAuthSnuttApiRepository = ({ else return { type: 'error', errcode: data.errcode }; }, passwordResetCheckEmail: async (body) => { - const { status, data } = await snuttApi['POST /v1/auth/password/reset/email/check']({ body }); + const { status, data } = await snuttApi['POST /v1/auth/password/reset/email/check']({ + body: { user_id: body.userId }, + }); if (status === 200) return { type: 'success', data }; else return { type: 'error', errcode: data.errcode }; }, sendPasswordResetVerificationEmail: async (body) => { - const { status, data } = await snuttApi['POST /v1/auth/password/reset/email/send']({ body }); + const { status, data } = await snuttApi['POST /v1/auth/password/reset/email/send']({ + body: { user_email: body.userEmail }, + }); if (status === 200) return { type: 'success', data }; else throw data; }, verifyPasswordResetCode: async (body) => { - const { status, data } = await snuttApi['POST /v1/auth/password/reset/verification/code']({ body }); + const { status, data } = await snuttApi['POST /v1/auth/password/reset/verification/code']({ + body: { user_id: body.userId, code: body.code }, + }); if (status === 200) return { type: 'success' }; else return { type: 'error', errcode: data.errcode }; }, resetPassword: async (body) => { - const { status, data } = await snuttApi['POST /v1/auth/password/reset']({ body }); + const { status, data } = await snuttApi['POST /v1/auth/password/reset']({ + body: { user_id: body.userId, password: body.password }, + }); if (status === 200) return { type: 'success', data }; else throw data; }, + deleteUser: async ({ token }) => { + const { status, data } = await snuttApi['DELETE /v1/user/account']({ token }); + if (status === 200) return { type: 'success' }; + return { type: 'error', errcode: data.errcode }; + }, + changePassword: async ({ oldPassword, newPassword, token }) => { + const { status, data } = await snuttApi['PUT /v1/user/password']({ + token, + body: { old_password: oldPassword, new_password: newPassword }, + }); + if (status === 200) return { type: 'success', data }; + return { type: 'error', errcode: data.errcode }; + }, }; }; diff --git a/apps/snutt-webclient/src/infrastructures/implUserSnuttApiRepository.ts b/apps/snutt-webclient/src/infrastructures/implUserSnuttApiRepository.ts new file mode 100644 index 00000000..d1d48c91 --- /dev/null +++ b/apps/snutt-webclient/src/infrastructures/implUserSnuttApiRepository.ts @@ -0,0 +1,46 @@ +import { type SnuttApi } from '@sf/snutt-api'; + +import { type getUserService } from '@/usecases/userService'; + +export const implUserSnuttApiRepository = ({ + snuttApi, +}: { + snuttApi: SnuttApi; +}): Parameters[0]['userRepository'] => { + return { + getUserInfo: async ({ token }) => { + const { status, data } = await snuttApi['GET /v1/user/info']({ token }); + if (status === 200) + return { + type: 'success', + data: { + email: data.email ?? null, + facebookName: data.fb_name ?? null, + isAdmin: data.isAdmin, + localId: data.local_id ?? null, + notificationCheckedAt: data.notificationCheckedAt, + regDate: data.regDate, + }, + }; + return { type: 'error', errcode: data.errcode }; + }, + addIdPassword: async ({ id, password, token }) => { + const { status, data } = await snuttApi['POST /v1/user/password']({ body: { id, password }, token }); + if (status === 200) return { type: 'success', data }; + return { type: 'error', errcode: data.errcode }; + }, + attachFacebookAccount: async ({ facebookId, facebookToken, token }) => { + const { status, data } = await snuttApi['POST /v1/user/facebook']({ + body: { fb_id: facebookId, fb_token: facebookToken }, + token, + }); + if (status === 200) return { type: 'success', data }; + return { type: 'error', errcode: data.errcode }; + }, + detachFacebookAccount: async ({ token }) => { + const { status, data } = await snuttApi['DELETE /v1/user/facebook']({ token }); + if (status === 200) return { type: 'success', data }; + return { type: 'error', errcode: data.errcode }; + }, + }; +}; diff --git a/apps/snutt-webclient/src/mocks/handlers/index.ts b/apps/snutt-webclient/src/mocks/handlers/index.ts index eeeb2f1a..dae76d42 100644 --- a/apps/snutt-webclient/src/mocks/handlers/index.ts +++ b/apps/snutt-webclient/src/mocks/handlers/index.ts @@ -22,7 +22,6 @@ import { } from '@/mocks/fixtures/timetable'; import { mockUsers } from '@/mocks/fixtures/user'; import type { TimetableRepository } from '@/repositories/timetableRepository'; -import type { UserRepository } from '@/repositories/userRepository'; import { withValidateAccess } from '../utils/access'; @@ -78,7 +77,7 @@ export const handlers = [ ), ), - http.get | CoreServerError>>( + http.get( `*/v1/user/info`, withValidateAccess(({ token }) => { const user = mockUsers.find((u) => u.auth.token === token); diff --git a/apps/snutt-webclient/src/pages/landing/landing-login/index.tsx b/apps/snutt-webclient/src/pages/landing/landing-login/index.tsx index 7daf73b5..2c295e39 100644 --- a/apps/snutt-webclient/src/pages/landing/landing-login/index.tsx +++ b/apps/snutt-webclient/src/pages/landing/landing-login/index.tsx @@ -34,7 +34,11 @@ export const LandingLogin = ({ className, authService, onSignUp }: Props) => { const handleFacebookSignIn = async (userInfo: ReactFacebookLoginInfo) => { setErrorMessage(''); - const res = await authService.signIn({ type: 'FACEBOOK', fb_id: userInfo.id, fb_token: userInfo.accessToken }); + const res = await authService.signIn({ + type: 'FACEBOOK', + facebookId: userInfo.id, + facebookToken: userInfo.accessToken, + }); if (res.type === 'success') saveToken(res.data.token, keepSignIn); else setErrorMessage(res.message); diff --git a/apps/snutt-webclient/src/pages/landing/landing-login/reset-password-dialog/index.tsx b/apps/snutt-webclient/src/pages/landing/landing-login/reset-password-dialog/index.tsx index ee1c4082..9c675577 100644 --- a/apps/snutt-webclient/src/pages/landing/landing-login/reset-password-dialog/index.tsx +++ b/apps/snutt-webclient/src/pages/landing/landing-login/reset-password-dialog/index.tsx @@ -69,7 +69,7 @@ export const LoginResetPasswordDialog = ({ open, onClose, authService }: Props) size="small" onClick={() => checkEmailMutation.mutate( - { user_id: id }, + { userId: id }, { onSuccess: ({ type }) => type === 'success' && setStep(Step.EMAIL_CONFIRM) }, ) } @@ -94,7 +94,7 @@ export const LoginResetPasswordDialog = ({ open, onClose, authService }: Props) onClick={() => { if (checkEmailMutation.data?.type !== 'success') return; sendCodeEmailMutation.mutate( - { user_email: checkEmailMutation.data.data.email }, + { userEmail: checkEmailMutation.data.data.email }, { onSuccess: () => setStep(Step.CODE_INPUT) }, ); }} @@ -122,7 +122,7 @@ export const LoginResetPasswordDialog = ({ open, onClose, authService }: Props) size="small" onClick={() => verifyCodeMutation.mutate( - { user_id: id, code }, + { userId: id, code }, { onSuccess: ({ type }) => type === 'success' && setStep(Step.RESET_PASSWORD) }, ) } @@ -148,7 +148,7 @@ export const LoginResetPasswordDialog = ({ open, onClose, authService }: Props) size="small" disabled={!password} onClick={() => - resetPasswordMutation.mutate({ user_id: id, password }, { onSuccess: () => setStep(Step.DONE) }) + resetPasswordMutation.mutate({ userId: id, password }, { onSuccess: () => setStep(Step.DONE) }) } > 완료 @@ -174,23 +174,23 @@ export const LoginResetPasswordDialog = ({ open, onClose, authService }: Props) }; const useCheckEmail = (authService: AuthService) => { - return useMutation({ mutationFn: (body: { user_id: string }) => authService.passwordResetCheckEmail(body) }); + return useMutation({ mutationFn: (body: { userId: string }) => authService.passwordResetCheckEmail(body) }); }; const useSendCodeEmail = (authService: AuthService) => { return useMutation({ - mutationFn: (body: { user_email: string }) => authService.sendPasswordResetVerificationEmail(body), + mutationFn: (body: { userEmail: string }) => authService.sendPasswordResetVerificationEmail(body), }); }; const useVerifyCode = (authService: AuthService) => { return useMutation({ - mutationFn: (body: { user_id: string; code: string }) => authService.verifyPasswordResetCode(body), + mutationFn: (body: { userId: string; code: string }) => authService.verifyPasswordResetCode(body), }); }; const useResetPassword = (authService: AuthService) => { - return useMutation({ mutationFn: (body: { user_id: string; password: string }) => authService.resetPassword(body) }); + return useMutation({ mutationFn: (body: { userId: string; password: string }) => authService.resetPassword(body) }); }; const Content = styled(Dialog.Content)` diff --git a/apps/snutt-webclient/src/pages/mypage/index.tsx b/apps/snutt-webclient/src/pages/mypage/index.tsx index 4c7d9aab..a5575a20 100644 --- a/apps/snutt-webclient/src/pages/mypage/index.tsx +++ b/apps/snutt-webclient/src/pages/mypage/index.tsx @@ -9,10 +9,9 @@ import { Button } from '@/components/button'; import { Layout } from '@/components/layout'; import { envContext } from '@/contexts/EnvContext'; import { serviceContext } from '@/contexts/ServiceContext'; +import { useTokenAuthContext } from '@/contexts/TokenAuthContext'; import { useTokenManageContext } from '@/contexts/TokenManageContext'; -import { type CoreServerError, getErrorMessage } from '@/entities/error'; import { useGuardContext } from '@/hooks/useGuardContext'; -import { queryKey } from '@/utils/query-key-factory'; import { MypageChangePassword } from './mypage-change-password'; import { MypageCloseAccountDialog } from './mypage-close-account-dialog'; @@ -30,7 +29,7 @@ export const MyPage = () => { const { mutate: attach } = useAttachFacebook(); const { mutate: detach } = useDetachFacebook(); - const isFbOnlyUser = myInfo && userService.isFbOnlyUser(myInfo); + const isFbOnlyUser = myInfo?.type === 'success' ? userService.isFbOnlyUser(myInfo.data) : undefined; const logout = () => { clearToken(); @@ -70,10 +69,10 @@ export const MyPage = () => { )}

- {myInfo?.local_id && ( + {myInfo?.type === 'success' && myInfo.data.localId && ( 페이스북 - {myInfo?.fb_name ? ( + {myInfo.data.facebookName ? ( @@ -114,35 +113,41 @@ export const MyPage = () => { const useMyInfo = () => { const { userService } = useGuardContext(serviceContext); + const { token } = useTokenAuthContext(); return useQuery({ - queryKey: queryKey('user/info'), - queryFn: () => userService.getUserInfo(), + queryKey: ['UserService', 'getUserInfo', { token }] as const, + queryFn: ({ queryKey }) => userService.getUserInfo(queryKey[2]), }); }; const useAttachFacebook = () => { const { saveToken } = useTokenManageContext(); const { userService } = useGuardContext(serviceContext); + const { token } = useTokenAuthContext(); return useMutation({ mutationFn: (userInfo: ReactFacebookLoginInfo) => { - return userService.attachFacebookAccount({ fb_id: userInfo.id, fb_token: userInfo.accessToken }); + return userService.attachFacebookAccount({ facebookId: userInfo.id, facebookToken: userInfo.accessToken, token }); + }, + onSuccess: (data) => { + if (data.type === 'success') saveToken(data.data.token, false); + else alert(data.message); }, - onSuccess: ({ token }) => saveToken(token, false), - onError: (error) => alert(getErrorMessage(error as unknown as CoreServerError)), }); }; const useDetachFacebook = () => { const { saveToken } = useTokenManageContext(); - + const { token } = useTokenAuthContext(); const { userService } = useGuardContext(serviceContext); return useMutation({ - mutationFn: () => userService.detachFacebookAccount(), - onSuccess: ({ token }) => saveToken(token, false), - onError: (error) => alert(getErrorMessage(error as unknown as CoreServerError)), + mutationFn: () => userService.detachFacebookAccount({ token }), + onSuccess: (data) => { + if (data.type === 'success') saveToken(token, false); + else alert(data.message); + }, }); }; diff --git a/apps/snutt-webclient/src/pages/mypage/mypage-change-password/index.tsx b/apps/snutt-webclient/src/pages/mypage/mypage-change-password/index.tsx index 7008084d..4f843240 100644 --- a/apps/snutt-webclient/src/pages/mypage/mypage-change-password/index.tsx +++ b/apps/snutt-webclient/src/pages/mypage/mypage-change-password/index.tsx @@ -5,11 +5,10 @@ import styled from 'styled-components'; import { Button } from '@/components/button'; import { ErrorDialog } from '@/components/error-dialog'; import { serviceContext } from '@/contexts/ServiceContext'; +import { useTokenAuthContext } from '@/contexts/TokenAuthContext'; import { useTokenManageContext } from '@/contexts/TokenManageContext'; -import { getErrorMessage } from '@/entities/error'; import { useErrorDialog } from '@/hooks/useErrorDialog'; import { useGuardContext } from '@/hooks/useGuardContext'; -import { get } from '@/utils/object/get'; export const MypageChangePassword = () => { const [currentPassword, setCurrentPassword] = useState(''); @@ -34,16 +33,19 @@ export const MypageChangePassword = () => { } mutate( - { old_password: currentPassword, new_password: newPassword }, + { oldPassword: currentPassword, newPassword }, { - onSuccess: ({ token }) => { - alert('비밀번호가 변경되었습니다.'); - setCurrentPassword(''); - setNewPassword(''); - setNewPasswordConfirm(''); - saveToken(token, false); + onSuccess: (data) => { + if (data.type === 'success') { + alert('비밀번호가 변경되었습니다.'); + setCurrentPassword(''); + setNewPassword(''); + setNewPasswordConfirm(''); + saveToken(data.data.token, false); + } else { + open(data.message); + } }, - onError: (err) => open(getErrorMessage({ errcode: get(err, ['errcode']) as number })), }, ); }; @@ -82,8 +84,9 @@ export const MypageChangePassword = () => { const useChangePassword = () => { const { authService } = useGuardContext(serviceContext); + const { token } = useTokenAuthContext(); return useMutation({ - mutationFn: (body: { old_password: string; new_password: string }) => authService.changePassword(body), + mutationFn: (body: { oldPassword: string; newPassword: string }) => authService.changePassword({ ...body, token }), }); }; diff --git a/apps/snutt-webclient/src/pages/mypage/mypage-close-account-dialog/index.tsx b/apps/snutt-webclient/src/pages/mypage/mypage-close-account-dialog/index.tsx index 076a90a7..e8865382 100644 --- a/apps/snutt-webclient/src/pages/mypage/mypage-close-account-dialog/index.tsx +++ b/apps/snutt-webclient/src/pages/mypage/mypage-close-account-dialog/index.tsx @@ -6,6 +6,7 @@ import styled from 'styled-components'; import { Button } from '@/components/button'; import { Dialog } from '@/components/dialog'; import { serviceContext } from '@/contexts/ServiceContext'; +import { useTokenAuthContext } from '@/contexts/TokenAuthContext'; import { useTokenManageContext } from '@/contexts/TokenManageContext'; import { useGuardContext } from '@/hooks/useGuardContext'; @@ -63,8 +64,10 @@ export const MypageCloseAccountDialog = ({ onClose, isOpen }: Props) => { const useCloseAccount = () => { const { authService } = useGuardContext(serviceContext); + const { token } = useTokenAuthContext(); + return useMutation({ - mutationFn: () => authService.closeAccount(), + mutationFn: () => authService.closeAccount({ token }), }); }; diff --git a/apps/snutt-webclient/src/pages/mypage/mypage-register-id/index.tsx b/apps/snutt-webclient/src/pages/mypage/mypage-register-id/index.tsx index e65a1800..44aaaeb3 100644 --- a/apps/snutt-webclient/src/pages/mypage/mypage-register-id/index.tsx +++ b/apps/snutt-webclient/src/pages/mypage/mypage-register-id/index.tsx @@ -5,6 +5,7 @@ import styled from 'styled-components'; import { Button } from '@/components/button'; import { ErrorDialog } from '@/components/error-dialog'; import { serviceContext } from '@/contexts/ServiceContext'; +import { useTokenAuthContext } from '@/contexts/TokenAuthContext'; import { useTokenManageContext } from '@/contexts/TokenManageContext'; import { getErrorMessage } from '@/entities/error'; import { useErrorDialog } from '@/hooks/useErrorDialog'; @@ -71,10 +72,12 @@ const useAddIdPassword = () => { const { saveToken } = useTokenManageContext(); const queryClient = useQueryClient(); const { userService } = useGuardContext(serviceContext); + const { token } = useTokenAuthContext(); return useMutation({ - mutationFn: (body: { id: string; password: string }) => userService.addIdPassword(body), - onSuccess: ({ token }) => { + mutationFn: (body: { id: string; password: string }) => userService.addIdPassword({ ...body, token }), + onSuccess: (data) => { + if (data.type === 'error') return; saveToken(token, false); return queryClient.invalidateQueries(); }, diff --git a/apps/snutt-webclient/src/repositories/userRepository.ts b/apps/snutt-webclient/src/repositories/userRepository.ts deleted file mode 100644 index acc3ec1b..00000000 --- a/apps/snutt-webclient/src/repositories/userRepository.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { HttpClient } from '@/clients/HttpClient'; -import type { User } from '@/entities/user'; - -export interface UserRepository { - getUserInfo(): Promise; - deleteUser(): Promise<{ message: 'ok' }>; - changePassword(body: { old_password: string; new_password: string }): Promise<{ token: string }>; - attachFacebookAccount(body: { fb_id: string; fb_token: string }): Promise<{ token: string }>; - detachFacebookAccount(): Promise<{ token: string }>; - addIdPassword(body: { id: string; password: string }): Promise<{ token: string }>; -} - -export const getUserRepository = ({ httpClient }: { httpClient: HttpClient }): UserRepository => { - return { - getUserInfo: async () => (await httpClient.get('/v1/user/info')).data, - deleteUser: async () => (await httpClient.delete<{ message: 'ok' }>('/v1/user/account')).data, - changePassword: async (body) => (await httpClient.put<{ token: string }>('/v1/user/password', body)).data, - addIdPassword: async (body) => (await httpClient.post<{ token: string }>('/v1/user/password', body)).data, - attachFacebookAccount: async (body) => (await httpClient.post<{ token: string }>('/v1/user/facebook', body)).data, - detachFacebookAccount: async () => (await httpClient.delete<{ token: string }>('/v1/user/facebook')).data, - }; -}; diff --git a/apps/snutt-webclient/src/usecases/authService.ts b/apps/snutt-webclient/src/usecases/authService.ts index a547e1fb..9fdb1eca 100644 --- a/apps/snutt-webclient/src/usecases/authService.ts +++ b/apps/snutt-webclient/src/usecases/authService.ts @@ -1,47 +1,58 @@ import { getErrorMessage } from '@/entities/error'; import { type RepositoryResponse, type UsecaseResponse } from '@/entities/response'; -import { type UserRepository } from '@/repositories/userRepository'; export interface AuthService { isValidPassword(password: string): boolean; - changePassword(body: { old_password: string; new_password: string }): Promise<{ token: string }>; + changePassword(body: { oldPassword: string; newPassword: string; token: string }): UsecaseResponse<{ token: string }>; signIn( - params: { type: 'LOCAL'; id: string; password: string } | { type: 'FACEBOOK'; fb_id: string; fb_token: string }, + params: + | { type: 'LOCAL'; id: string; password: string } + | { type: 'FACEBOOK'; facebookId: string; facebookToken: string }, ): UsecaseResponse<{ token: string }>; signUp(body: { id: string; password: string }): UsecaseResponse<{ token: string }>; - closeAccount(): Promise<{ message: 'ok' }>; + closeAccount(_: { token: string }): UsecaseResponse; findIdByEmail(body: { email: string }): UsecaseResponse; - passwordResetCheckEmail(body: { user_id: string }): UsecaseResponse<{ email: string }>; - sendPasswordResetVerificationEmail(body: { user_email: string }): Promise<{ message: 'ok' }>; - verifyPasswordResetCode(body: { user_id: string; code: string }): UsecaseResponse; - resetPassword(body: { user_id: string; password: string }): Promise<{ message: 'ok' }>; + passwordResetCheckEmail(body: { userId: string }): UsecaseResponse<{ email: string }>; + sendPasswordResetVerificationEmail(body: { userEmail: string }): Promise<{ message: 'ok' }>; + verifyPasswordResetCode(body: { userId: string; code: string }): UsecaseResponse; + resetPassword(body: { userId: string; password: string }): Promise<{ message: 'ok' }>; } -type Deps = { +export const getAuthService = ({ + authRepository, +}: { authRepository: { signInWithIdPassword(args: { id: string; password: string }): RepositoryResponse<{ token: string }>; - signInWithFacebook(args: { fb_id: string; fb_token: string }): RepositoryResponse<{ token: string }>; + signInWithFacebook(args: { facebookId: string; facebookToken: string }): RepositoryResponse<{ token: string }>; signUpWithIdPassword(body: { id: string; password: string }): RepositoryResponse<{ token: string }>; findId(body: { email: string }): RepositoryResponse; - passwordResetCheckEmail(body: { user_id: string }): RepositoryResponse<{ email: string }>; - sendPasswordResetVerificationEmail(body: { user_email: string }): RepositoryResponse<{ message: 'ok' }>; - verifyPasswordResetCode(body: { user_id: string; code: string }): RepositoryResponse; - resetPassword(body: { user_id: string; password: string }): RepositoryResponse<{ message: 'ok' }>; + passwordResetCheckEmail(body: { userId: string }): RepositoryResponse<{ email: string }>; + sendPasswordResetVerificationEmail(body: { userEmail: string }): RepositoryResponse<{ message: 'ok' }>; + verifyPasswordResetCode(body: { userId: string; code: string }): RepositoryResponse; + resetPassword(body: { userId: string; password: string }): RepositoryResponse<{ message: 'ok' }>; + deleteUser(_: { token: string }): RepositoryResponse; + changePassword(body: { + oldPassword: string; + newPassword: string; + token: string; + }): RepositoryResponse<{ token: string }>; }; - userRepository: UserRepository; -}; -export const getAuthService = ({ authRepository, userRepository }: Deps): AuthService => { +}): AuthService => { return { isValidPassword: (password) => password.split('').some((item) => /[0-9]+/.test(item)) && password.split('').some((item) => /[a-zA-Z]+/.test(item)) && password.length >= 6 && password.length <= 20, - changePassword: async (body) => userRepository.changePassword(body), + changePassword: async (body) => { + const data = await authRepository.changePassword(body); + if (data.type === 'success') return { type: 'success', data: data.data }; + else return { type: 'error', message: getErrorMessage(data) }; + }, signIn: async (params) => { const data = await (params.type === 'LOCAL' ? authRepository.signInWithIdPassword({ id: params.id, password: params.password }) - : authRepository.signInWithFacebook({ fb_id: params.fb_id, fb_token: params.fb_token })); + : authRepository.signInWithFacebook({ facebookId: params.facebookId, facebookToken: params.facebookToken })); if (data.type === 'success') return { type: 'success', data: data.data }; else return { type: 'error', message: getErrorMessage(data) }; @@ -51,7 +62,11 @@ export const getAuthService = ({ authRepository, userRepository }: Deps): AuthSe if (data.type === 'success') return { type: 'success', data: data.data }; else return { type: 'error', message: getErrorMessage(data) }; }, - closeAccount: () => userRepository.deleteUser(), + closeAccount: async ({ token }) => { + const data = await authRepository.deleteUser({ token }); + if (data.type === 'success') return { type: 'success' }; + else return { type: 'error', message: getErrorMessage(data) }; + }, findIdByEmail: async (body) => { const data = await authRepository.findId(body); if (data.type === 'success') return { type: 'success' }; diff --git a/apps/snutt-webclient/src/usecases/userService.ts b/apps/snutt-webclient/src/usecases/userService.ts index 6cecd012..18c51510 100644 --- a/apps/snutt-webclient/src/usecases/userService.ts +++ b/apps/snutt-webclient/src/usecases/userService.ts @@ -1,21 +1,54 @@ +import { getErrorMessage } from '@/entities/error'; +import { type RepositoryResponse, type UsecaseResponse } from '@/entities/response'; import { type User } from '@/entities/user'; -import { type UserRepository } from '@/repositories/userRepository'; export interface UserService { - getUserInfo(): Promise; - addIdPassword(body: { id: string; password: string }): Promise<{ token: string }>; - attachFacebookAccount(body: { fb_id: string; fb_token: string }): Promise<{ token: string }>; - detachFacebookAccount(): Promise<{ token: string }>; + getUserInfo(_: { token: string }): UsecaseResponse; + addIdPassword(body: { id: string; password: string; token: string }): UsecaseResponse<{ token: string }>; + attachFacebookAccount(body: { + facebookId: string; + facebookToken: string; + token: string; + }): UsecaseResponse<{ token: string }>; + detachFacebookAccount(_: { token: string }): UsecaseResponse<{ token: string }>; isFbOnlyUser(user: User): boolean; } -type Deps = { repositories: [UserRepository] }; -export const getUserService = ({ repositories: [userRepository] }: Deps): UserService => { +export const getUserService = ({ + userRepository, +}: { + userRepository: { + getUserInfo(_: { token: string }): RepositoryResponse; + attachFacebookAccount(body: { + facebookId: string; + facebookToken: string; + token: string; + }): RepositoryResponse<{ token: string }>; + detachFacebookAccount(_: { token: string }): RepositoryResponse<{ token: string }>; + addIdPassword(body: { id: string; password: string; token: string }): RepositoryResponse<{ token: string }>; + }; +}): UserService => { return { - getUserInfo: () => userRepository.getUserInfo(), - addIdPassword: (body) => userRepository.addIdPassword(body), - attachFacebookAccount: (body: { fb_id: string; fb_token: string }) => userRepository.attachFacebookAccount(body), - detachFacebookAccount: () => userRepository.detachFacebookAccount(), - isFbOnlyUser: (user) => !!user.fb_name && !user.local_id, + getUserInfo: async ({ token }) => { + const data = await userRepository.getUserInfo({ token }); + if (data.type === 'success') return { type: 'success', data: data.data }; + else return { type: 'error', message: getErrorMessage(data) }; + }, + addIdPassword: async (body) => { + const data = await userRepository.addIdPassword(body); + if (data.type === 'success') return { type: 'success', data: data.data }; + else return { type: 'error', message: getErrorMessage(data) }; + }, + attachFacebookAccount: async (body) => { + const data = await userRepository.attachFacebookAccount(body); + if (data.type === 'success') return { type: 'success', data: data.data }; + else return { type: 'error', message: getErrorMessage(data) }; + }, + detachFacebookAccount: async ({ token }) => { + const data = await userRepository.detachFacebookAccount({ token }); + if (data.type === 'success') return { type: 'success', data: data.data }; + else return { type: 'error', message: getErrorMessage(data) }; + }, + isFbOnlyUser: (user) => !!user.facebookName && !user.localId, }; }; diff --git a/packages/snutt-api/src/apis/snutt-timetable/index.ts b/packages/snutt-api/src/apis/snutt-timetable/index.ts index 35c99a24..cfe9f25e 100644 --- a/packages/snutt-api/src/apis/snutt-timetable/index.ts +++ b/packages/snutt-api/src/apis/snutt-timetable/index.ts @@ -1,6 +1,14 @@ import { Api, GetApiSpecsParameter } from '..'; import { SuccessResponse, ErrorResponse } from '../../response'; -import { LocalLoginRequest, LoginResponse, NotificationResponse, SearchQueryLegacy, LectureDto } from './schemas'; +import { + LocalLoginRequest, + LoginResponse, + NotificationResponse, + SearchQueryLegacy, + LectureDto, + UserLegacyDto, + OkResponse, +} from './schemas'; export const getSnuttTimetableApis = ({ callWithToken, callWithoutToken }: GetApiSpecsParameter) => ({ @@ -11,11 +19,23 @@ export const getSnuttTimetableApis = ({ callWithToken, callWithoutToken }: GetAp body, }), 'POST /v1/auth/register_local': ({ body }: { body: { id: string; password: string } }) => - callWithoutToken | ErrorResponse<403, 1229>>({ + callWithoutToken | ErrorResponse<403, 1229>>({ method: 'post', path: `/v1/auth/register_local`, body, }), + 'DELETE /v1/user/account': ({ token }: { token: string }) => + callWithToken>({ + method: 'delete', + path: `/v1/user/account`, + token, + }), + 'GET /v1/user/info': ({ token }: { token: string }) => + callWithToken>({ + method: 'get', + path: `/v1/user/info`, + token, + }), 'GET /v1/notification': ({ token }: { token: string }) => callWithToken>({ method: 'get', diff --git a/packages/snutt-api/src/apis/snutt/index.ts b/packages/snutt-api/src/apis/snutt/index.ts index 9dde7e2e..3709ec2c 100644 --- a/packages/snutt-api/src/apis/snutt/index.ts +++ b/packages/snutt-api/src/apis/snutt/index.ts @@ -79,4 +79,37 @@ export const getSnuttApis = ({ callWithToken, callWithoutToken }: GetApiSpecsPar path: `/v1/tags/${params.year}/${params.semester}`, token, }), + 'PUT /v1/user/password': ({ + body, + token, + }: { + body: { old_password: string; new_password: string }; + token: string; + }) => + callWithToken>({ + method: 'put', + path: `/v1/user/password`, + body, + token, + }), + 'POST /v1/user/password': ({ body, token }: { body: { id: string; password: string }; token: string }) => + callWithToken>({ + method: 'post', + path: `/v1/user/password`, + body, + token, + }), + 'POST /v1/user/facebook': ({ body, token }: { body: { fb_id: string; fb_token: string }; token: string }) => + callWithToken>({ + method: 'post', + path: `/v1/user/facebook`, + body, + token, + }), + 'DELETE /v1/user/facebook': ({ token }: { token: string }) => + callWithToken>({ + method: 'delete', + path: `/v1/user/facebook`, + token, + }), }) satisfies Record;