diff --git a/.openapirc.json b/.openapirc.json index 33a5156..ebde31e 100644 --- a/.openapirc.json +++ b/.openapirc.json @@ -1,5 +1,5 @@ { - "file": "./src/shared/api/openapi.yaml", + "file": "https://bump.sh/undrcrxwn/doc/crowdparlay.yaml", "outputDir": "./src/shared/api/", "templateFileNameCode": "client.ts", "presets": [ diff --git a/src/pages/discussion/model.ts b/src/pages/discussion/model.ts index ded8964..f24907c 100644 --- a/src/pages/discussion/model.ts +++ b/src/pages/discussion/model.ts @@ -1,29 +1,28 @@ import * as typed from 'typed-contracts'; import {chainRoute} from 'atomic-router'; -import {createEvent, createStore, sample} from 'effector'; -import {attach} from 'effector/compat'; +import {attach, createEvent, createStore, sample} from 'effector'; import {produce} from 'immer'; import { - apiV1CommentsGet, + apiV1CommentsGetFx, apiV1CommentsGetOk, ApiV1CommentsParentCommentIdRepliesGet, - apiV1CommentsParentCommentIdRepliesGet, + apiV1CommentsParentCommentIdRepliesGetFx, apiV1CommentsParentCommentIdRepliesGetOk, ApiV1CommentsParentCommentIdRepliesPost, - apiV1CommentsParentCommentIdRepliesPost, + apiV1CommentsParentCommentIdRepliesPostFx, ApiV1CommentsPost, - apiV1CommentsPost, - apiV1DiscussionsDiscussionIdGet, + apiV1CommentsPostFx, + apiV1DiscussionsDiscussionIdGetFx, apiV1DiscussionsDiscussionIdGetOk, } from '~/shared/api'; import {routes} from '~/shared/routes'; -const getDiscussionFx = attach({effect: apiV1DiscussionsDiscussionIdGet}); -const getCommentsFx = attach({effect: apiV1CommentsGet}); -const commentDiscussionFx = attach({effect: apiV1CommentsPost}); -const getRepliesFx = attach({effect: apiV1CommentsParentCommentIdRepliesGet}); -const commentReplyFx = attach({effect: apiV1CommentsParentCommentIdRepliesPost}); +const getDiscussionFx = attach({effect: apiV1DiscussionsDiscussionIdGetFx}); +const getCommentsFx = attach({effect: apiV1CommentsGetFx}); +const commentDiscussionFx = attach({effect: apiV1CommentsPostFx}); +const getRepliesFx = attach({effect: apiV1CommentsParentCommentIdRepliesGetFx}); +const commentReplyFx = attach({effect: apiV1CommentsParentCommentIdRepliesPostFx}); export const currentRoute = routes.discussion; @@ -45,10 +44,13 @@ export const $discussion = createStore>([]); +export const $comments = createStore['items']>([]); export const commentFormSubmit = createEvent(); -export type Replies = Record>; +export type Replies = Record< + string, + typed.Get['items'] +>; export const $replies = createStore({}); export const replyClicked = createEvent(); export const replyFormSubmit = createEvent(); @@ -64,8 +66,8 @@ sample({ fn: ({answer}) => ({ query: { discussionId: answer.id!, - size: 100, - page: 0, + offset: 0, + count: 20, }, }), target: getCommentsFx, @@ -73,7 +75,7 @@ sample({ sample({ clock: getCommentsFx.doneData, - fn: (x) => x.answer, + fn: (x) => x.answer.items, target: $comments, }); @@ -89,8 +91,8 @@ sample({ fn: (discussion) => ({ query: { discussionId: discussion.id!, - size: 100, - page: 0, + offset: 0, + count: 20, }, }), target: getCommentsFx, @@ -107,7 +109,7 @@ sample({ fn: (replies, {params, result}) => produce(replies, (draft) => { // @ts-ignore - draft[params.path.parentCommentId] = result.answer; + draft[params.path.parentCommentId] = result.answer.items; }), target: $replies, }); @@ -124,8 +126,8 @@ sample({ parentCommentId: params.path.parentCommentId, }, query: { - page: 0, - size: 100, + offset: 0, + count: 20, }, }), target: getRepliesFx, diff --git a/src/pages/discussion/page.tsx b/src/pages/discussion/page.tsx index 5780628..982d0dc 100644 --- a/src/pages/discussion/page.tsx +++ b/src/pages/discussion/page.tsx @@ -47,7 +47,7 @@ const buildReplyTree = ( canReply={true} canReport={true} onShownClick={(parentCommentId) => - onReplyClicked({path: {parentCommentId}, query: {page: 0, size: 100}}) + onReplyClicked({path: {parentCommentId}, query: {offset: 0, count: 100}}) } onReplyFormSubmit={(payload) => onReplyFormSubmit({ @@ -72,33 +72,6 @@ export const DiscussionPage = () => { model.replyFormSubmit, ]); - // const comments = useList(model.$comments, (comment) => ( - // ({ - // id: x.id!, - // username: x.username!, - // displayName: x.display_name!, - // avatarUrl: x.avatar_url!, - // }))} - // commentsCount={comment.reply_count!} - // canReply={true} - // canReport={true} - // onShownClick={(parentCommentId) => - // onReplyClicked({path: {parentCommentId}, query: {page: 0, size: 20}}) - // } - // /> - // )); - const items = comments.map((comment) => { const children = buildReplyTree(comment.id!, replies, onReplyClicked, onReplyFormSubmit); @@ -124,7 +97,7 @@ export const DiscussionPage = () => { canReply={true} canReport={true} onShownClick={(parentCommentId) => - onReplyClicked({path: {parentCommentId}, query: {page: 0, size: 100}}) + onReplyClicked({path: {parentCommentId}, query: {offset: 0, count: 100}}) } onReplyFormSubmit={(payload) => onReplyFormSubmit({ diff --git a/src/pages/profile/model.ts b/src/pages/profile/model.ts index 94fb783..04408ed 100644 --- a/src/pages/profile/model.ts +++ b/src/pages/profile/model.ts @@ -3,15 +3,15 @@ import {chainRoute} from 'atomic-router'; import {attach, createStore, sample} from 'effector'; import { - apiV1DiscussionsGet, + apiV1DiscussionsGetFx, apiV1DiscussionsGetOk, - apiV1UsersResolveGet, + apiV1UsersResolveGetFx, apiV1UsersResolveGetOk, } from '~/shared/api'; import {routes} from '~/shared/routes'; -const getUserFx = attach({effect: apiV1UsersResolveGet}); -const getDiscussionsFx = attach({effect: apiV1DiscussionsGet}); +const getUserFx = attach({effect: apiV1UsersResolveGetFx}); +const getDiscussionsFx = attach({effect: apiV1DiscussionsGetFx}); export const currentRoute = routes.profile; @@ -31,7 +31,7 @@ export const dataLoadedRoute = chainRoute({ export const $user = createStore | null>(null); -export const $discussions = createStore>([]); +export const $discussions = createStore['items']>([]); sample({ clock: getUserFx.doneData, @@ -44,6 +44,8 @@ sample({ fn: ({answer}) => ({ query: { authorId: answer.id!, + count: 20, + offset: 0, }, }), target: getDiscussionsFx, @@ -51,6 +53,6 @@ sample({ sample({ clock: getDiscussionsFx.doneData, - fn: (x) => x.answer, + fn: (x) => x.answer.items, target: $discussions, }); diff --git a/src/pages/profile/page.tsx b/src/pages/profile/page.tsx index 3233c8c..4cfe98f 100644 --- a/src/pages/profile/page.tsx +++ b/src/pages/profile/page.tsx @@ -1,5 +1,7 @@ import {useList, useUnit} from 'effector-react'; +import {NotFoundPage} from '~/pages/not-found/page'; + import { Avatar, Button, diff --git a/src/pages/sign-in/assets/google.svg b/src/pages/sign-in/assets/google.svg new file mode 100644 index 0000000..4eb6e67 --- /dev/null +++ b/src/pages/sign-in/assets/google.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/pages/sign-in/model.ts b/src/pages/sign-in/model.ts index 37bf189..90869e9 100644 --- a/src/pages/sign-in/model.ts +++ b/src/pages/sign-in/model.ts @@ -1,11 +1,12 @@ -import {attach, createStore} from 'effector'; +import {attach, createEvent, createStore} from 'effector'; import {sample} from 'effector'; import {createForm} from 'effector-forms'; +import {createEffect} from 'effector/effector.umd'; import {or} from 'patronum'; import * as api from '~/shared/api'; -import {LOCAL_STORAGE_ACCESS_TOKEN_KEY, LOCAL_STORAGE_REFRESH_TOKEN_KEY} from '~/shared/config'; -import {routes} from '~/shared/routes'; +import {ApiV1AuthenticationSignInPost, ApiV1AuthenticationSsoGoogleGet} from '~/shared/api'; +import {router, routes} from '~/shared/routes'; import {rules} from '~/shared/rules'; import {chainAnonymous, sessionRequestFx} from '~/shared/session'; @@ -14,7 +15,15 @@ export const anonymousRoute = chainAnonymous(currentRoute, { otherwise: routes.explore.open, }); -const signInFx = attach({effect: api.signInFx}); +const signInFx = attach({effect: api.apiV1AuthenticationSignInPostFx}); +const signInWithGoogleFx = attach({effect: api.apiV1AuthenticationSsoGoogleGetFx}); +const navigateToUrlFx = createEffect((url: string) => { + console.log(url); + router.push({path: url, params: {}, query: {}, method: 'replace'}); +}); + +export const signInWithGoogleClicked = createEvent(); + export const $loading = or(signInFx.pending, sessionRequestFx.pending); export const $form = createForm({ @@ -42,40 +51,48 @@ sample({ sample({ clock: $form.formValidated, + fn: (payload): ApiV1AuthenticationSignInPost => ({ + body: { + usernameOrEmail: payload.username, + password: payload.password, + }, + }), target: [signInFx, $formError.reinit], }); sample({ - clock: signInFx.doneData, - filter: (res) => res.status >= 300 && res.status === 400 && Boolean(res.body.validation_errors), + clock: signInFx.failData, fn: (res) => { - return Object.entries(res.body.validation_errors!).map(([field, errorText]) => { - return { - field, - rule: 'backend', - errorText: errorText as string, - }; - }); + if ('error_description' in res.error) { + return res.error.error_description!; + } + return 'Something went wrong. Try again later.'; }, - target: $form.addErrors, + target: $formError, }); sample({ clock: signInFx.doneData, - filter: (res) => - res.status >= 300 && !(res.status === 400 && Boolean(res.body.validation_errors)), - fn: (res) => { - return res.body.error_description ?? 'Something went wrong. Try again later.'; - }, - target: $formError, + source: currentRoute.$query, + filter: (query) => query.returnUrl === undefined, + target: sessionRequestFx, }); sample({ clock: signInFx.doneData, - filter: (res) => res.status < 300, - fn: (data) => { - localStorage.setItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY, data.body.access_token); - localStorage.setItem(LOCAL_STORAGE_REFRESH_TOKEN_KEY, data.body.refresh_token); - }, - target: sessionRequestFx, + source: currentRoute.$query, + filter: (query) => query.returnUrl !== undefined, + fn: (query) => query.returnUrl!, + target: navigateToUrlFx, +}); + +sample({ + clock: signInWithGoogleClicked, + source: currentRoute.$query, + fn: (state): ApiV1AuthenticationSsoGoogleGet => ({ + query: { + returnUrl: state.returnUrl, + }, + }), + target: signInWithGoogleFx, }); diff --git a/src/pages/sign-in/page.module.scss b/src/pages/sign-in/page.module.scss index 064dac7..5b9b6eb 100644 --- a/src/pages/sign-in/page.module.scss +++ b/src/pages/sign-in/page.module.scss @@ -14,3 +14,18 @@ line-height: 77px; color: var(--color-white); } + +.external { + margin-top: -27px; + display: flex; + flex-direction: column; + gap: 25px; +} + +.or { + line-height: 19px; +} + +.button { + gap: 10px; +} \ No newline at end of file diff --git a/src/pages/sign-in/page.tsx b/src/pages/sign-in/page.tsx index 36b5077..2e60149 100644 --- a/src/pages/sign-in/page.tsx +++ b/src/pages/sign-in/page.tsx @@ -5,6 +5,7 @@ import {routes} from '~/shared/routes'; import { Attention, Button, + ButtonVariant, Container, ContainerSize, Form, @@ -15,13 +16,16 @@ import { TextSize, } from '~/shared/ui'; -import {$form, $formError, $loading} from './model'; +import GoogleIcon from './assets/google.svg'; +import {$form, $formError, $loading, signInWithGoogleClicked} from './model'; import cls from './page.module.scss'; export const SignInPage = () => { const {fields, submit, eachValid} = useForm($form); const [loading, formError] = useUnit([$loading, $formError]); + const [onSignInWithGoogleClicked] = useUnit([signInWithGoogleClicked]); + return ( @@ -58,13 +62,30 @@ export const SignInPage = () => { -
- - Reset password - - - Sign up - +
+ + or + + +
+ +
+ +
+ + Reset password + + + Sign up + +
diff --git a/src/pages/sign-up/model.ts b/src/pages/sign-up/model.ts index 5ddecdf..5bfc053 100644 --- a/src/pages/sign-up/model.ts +++ b/src/pages/sign-up/model.ts @@ -1,18 +1,33 @@ import {attach, createStore, sample} from 'effector'; import {createForm} from 'effector-forms'; -import {or} from 'patronum'; +import {or, spread} from 'patronum'; import * as api from '~/shared/api'; +import {ApiV1UsersRegisterPost} from '~/shared/api'; import {routes} from '~/shared/routes'; import {rules} from '~/shared/rules'; import {chainAnonymous, sessionRequestFx} from '~/shared/session'; +enum Provider { + GOOGLE = 'google', +} + export const currentRoute = routes.auth.signUp; export const anonymousRoute = chainAnonymous(currentRoute, { otherwise: routes.explore.open, }); -const signUpFx = attach({effect: api.apiV1UsersRegisterPost}); +const signUpFx = attach({effect: api.apiV1UsersRegisterPostFx}); + +export const $provider = currentRoute.$query.map((query) => { + const provider = query.provider ?? null; + if (provider === null) { + return null; + } + + return Object.values(Provider).find((p) => p === provider) ?? null; +}); + export const $loading = or(signUpFx.pending, sessionRequestFx.pending); export const $form = createForm({ @@ -57,6 +72,23 @@ export const $form = createForm({ export const $formError = createStore(null); +sample({ + clock: $provider, + filter: (provider) => provider !== null, + fn: (payload) => ({ + email: `provider@${payload}.com`, + password: 'provider', + confirm_password: 'provider', + }), + target: spread({ + targets: { + email: $form.fields.email.set, + password: $form.fields.password.set, + confirm_password: $form.fields.confirm_password.set, + }, + }), +}); + sample({ clock: $form.fields.password.changed, source: $form.fields.confirm_password.$isDirty, @@ -64,14 +96,9 @@ sample({ target: $form.fields.confirm_password.validate, }); -sample({ - clock: anonymousRoute.closed, - target: [$form.reset, $formError.reinit], -}); - sample({ clock: $form.formValidated, - fn: (fields) => ({body: fields}), + fn: (fields): ApiV1UsersRegisterPost => ({body: fields}), target: [signUpFx, $formError.reinit], }); @@ -111,8 +138,10 @@ sample({ sample({ clock: signUpFx.doneData, - fn: (data) => { - console.log(data); - }, target: sessionRequestFx, }); + +sample({ + clock: anonymousRoute.closed, + target: [$form.reset, $formError.reinit], +}); diff --git a/src/pages/sign-up/page.tsx b/src/pages/sign-up/page.tsx index 4995e88..9423349 100644 --- a/src/pages/sign-up/page.tsx +++ b/src/pages/sign-up/page.tsx @@ -17,13 +17,15 @@ import { TextSize, } from '~/shared/ui'; -import {$form, $formError, $loading} from './model'; +import {$form, $formError, $loading, $provider} from './model'; import cls from './page.module.scss'; export const SignUpPage = () => { const {fields, submit, eachValid} = useForm($form); const [loading, formError] = useUnit([$loading, $formError]); + const [provider] = useUnit([$provider]); + return ( @@ -32,17 +34,20 @@ export const SignUpPage = () => {
- fields.email?.onChange(e.target.value)} - isInvalid={fields.email?.hasError()} - errorMessage={fields.email?.errorText()} - /> + {provider === null && ( + fields.email?.onChange(e.target.value)} + isInvalid={fields.email?.hasError()} + errorMessage={fields.email?.errorText()} + /> + )} + { isInvalid={fields.display_name?.hasError()} errorMessage={fields.display_name?.errorText()} /> - fields.password?.onChange(e.target.value)} - isInvalid={fields.password?.hasError()} - errorMessage={fields.password?.errorText()} - /> - fields.confirm_password?.onChange(e.target.value)} - isInvalid={fields.confirm_password?.hasError()} - errorMessage={fields.confirm_password?.errorText()} - /> + + {provider === null && ( + fields.password?.onChange(e.target.value)} + isInvalid={fields.password?.hasError()} + errorMessage={fields.password?.errorText()} + /> + )} + {provider === null && ( + fields.confirm_password?.onChange(e.target.value)} + isInvalid={fields.confirm_password?.hasError()} + errorMessage={fields.confirm_password?.errorText()} + /> + )} { - const accessToken = localStorage.getItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY); - if (accessToken) { - config.headers.Authorization = `Bearer ${accessToken}`; - } - return config; -}); - -localApi.interceptors.response.use( - (config) => { - return config; - }, - async (error) => { - const originalRequest = error.config; - - if (error.response.status === 401 && error.config && !error.config._isRetry) { - originalRequest._isRetry = true; - - const refreshToken = localStorage.getItem(LOCAL_STORAGE_REFRESH_TOKEN_KEY); - if (!refreshToken) { - throw error; - } - - const {data} = await axios.post('/connect/token', { - grant_type: 'refresh_token', - refresh_token: refreshToken, - scope: 'offline_access', - }); - - localStorage.setItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY, data.access_token); - localStorage.setItem(LOCAL_STORAGE_REFRESH_TOKEN_KEY, data.refresh_token); - - return localApi.request(originalRequest); - } - - throw error; - }, -); - -export interface SignIn { - username: string; - password: string; -} - -export interface ConnectToken { - status: number; - body: { - access_token: string; - refresh_token: string; - expires_in: number; - scope: string; - token_type: string; - - validation_errors?: NonNullable; - error_description?: string; - }; -} - -export const signInFx = createEffect((form) => { - return requestFx({ - method: 'POST', - path: '/connect/token', - body: new URLSearchParams({ - grant_type: 'password', - scope: 'offline_access', - ...form, - }), - }); -}); - -export const logoutFx = createEffect(() => { - localStorage.removeItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY); - localStorage.removeItem(LOCAL_STORAGE_REFRESH_TOKEN_KEY); -}); diff --git a/src/shared/api/client.ts b/src/shared/api/client.ts index b6093dd..abaddac 100644 --- a/src/shared/api/client.ts +++ b/src/shared/api/client.ts @@ -1,12 +1,16 @@ +// @ts-nocheck + // Crowd Parlay API v1 // --- -// This file is automatically generated by openapi with preset openapi-preset-effector +// This file is automatically generated by openapi with preset effector-openapi-preset // Do not edit this file directly. Instead open openapi config file and follow the link in "file" import { createEffect } from 'effector'; import * as typed from 'typed-contracts'; import { requestFx } from './request'; //#region prebuilt code +const custom = { any: (valueName: string, value: unknown): any => value } + export type GenericErrors = | { status: 'unexpected'; @@ -21,7 +25,7 @@ export type GenericErrors = error: typed.ValidationError; }; -type ErrorCodes = 400 | 401 | 402 | 403 | 404 | 405 | 406 | 500 | 501 | 502 | 505; +type ErrorCodes = 400 | 401 | 402 | 403 | 404 | 405 | 406 | 500 | 501 | 502 | 503 | 505; /** * @throws */ @@ -59,94 +63,73 @@ function parseByStatus< return { status, answer } as Result[Exclude]; } -//#endregion prebuilt code/* --- */ -//#region apiV1AuthorsAuthorIdGet -export type ApiV1AuthorsAuthorIdGet = { - path: { - authorId: string; - }; -}; -/* Success */ -export const apiV1AuthorsAuthorIdGetOk = typed.object({ - id: typed.string.optional, - username: typed.string.optional, - display_name: typed.string.optional, - avatar_url: typed.string.maybe -}); -export type ApiV1AuthorsAuthorIdGetDone = { - status: "ok"; - answer: typed.Get; -}; -/* Not Found */ -export const apiV1AuthorsAuthorIdGetNotFound = typed.object({ - error_description: typed.string.optional -}); -/* Server Error */ -export const apiV1AuthorsAuthorIdGetInternalServerError = typed.object({ - error_description: typed.string.optional -}); -export type ApiV1AuthorsAuthorIdGetFail = { - status: "not_found"; - error: typed.Get; -} | { - status: "internal_server_error"; - error: typed.Get; -} | GenericErrors; -export const apiV1AuthorsAuthorIdGet = createEffect({ - async handler({ - path - }) { - const name = "apiV1AuthorsAuthorIdGet.body"; - const response = await requestFx({ - path: `/api/v1/authors/${path.authorId}`, - method: "GET" - }); - return parseByStatus(name, response, { - 200: ["ok", apiV1AuthorsAuthorIdGetOk], - 404: ["not_found", apiV1AuthorsAuthorIdGetNotFound], - 500: ["internal_server_error", apiV1AuthorsAuthorIdGetInternalServerError] - }); +function convertBodyToUrlSearchParams(body: Record): URLSearchParams { + function flattenObject(obj: any, parent: string = '', res: Record = {}): Record { + for (let key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + const propName = parent ? `${parent}[${key}]` : key; + if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) { + flattenObject(obj[key], propName, res); + } else { + res[propName] = String(obj[key]); + } + } + } + return res; } -}); -//#endregion apiV1AuthorsAuthorIdGet -/* --- */ + const flatBody = flattenObject(body); + return new URLSearchParams(flatBody); +} + +//#endregion prebuilt code/* --- */ //#region apiV1CommentsCommentIdGet export type ApiV1CommentsCommentIdGet = { path: { commentId: string; }; }; -/* Success */ + +/* OK */ export const apiV1CommentsCommentIdGetOk = typed.object({ - id: typed.string.optional, - content: typed.string.optional, - author: typed.object({ - id: typed.string.optional, - username: typed.string.optional, - display_name: typed.string.optional, + id: typed.string, + content: typed.string, + author: typed.intersection(typed.object({ + id: typed.string, + username: typed.string, + display_name: typed.string, avatar_url: typed.string.maybe - }).optional, - created_at: typed.string.optional, - reply_count: typed.number.optional, + })).maybe, + created_at: typed.string, + reply_count: typed.number, first_replies_authors: typed.array(typed.object({ - id: typed.string.optional, - username: typed.string.optional, - display_name: typed.string.optional, + id: typed.string, + username: typed.string, + display_name: typed.string, avatar_url: typed.string.maybe - })).optional + })) }); export type ApiV1CommentsCommentIdGetDone = { status: "ok"; answer: typed.Get; }; + /* Not Found */ export const apiV1CommentsCommentIdGetNotFound = typed.object({ - error_description: typed.string.optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe }); -/* Server Error */ + +/* Internal Server Error */ export const apiV1CommentsCommentIdGetInternalServerError = typed.object({ - error_description: typed.string.optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe }); export type ApiV1CommentsCommentIdGetFail = { status: "not_found"; @@ -155,11 +138,11 @@ export type ApiV1CommentsCommentIdGetFail = { status: "internal_server_error"; error: typed.Get; } | GenericErrors; -export const apiV1CommentsCommentIdGet = createEffect({ +export const apiV1CommentsCommentIdGetFx = createEffect({ async handler({ path }) { - const name = "apiV1CommentsCommentIdGet.body"; + const name = "apiV1CommentsCommentIdGetFx.body"; const response = await requestFx({ path: `/api/v1/comments/${path.commentId}`, method: "GET" @@ -170,6 +153,7 @@ export const apiV1CommentsCommentIdGet = createEffect; }; + /* Bad Request */ export const apiV1CommentsGetBadRequest = typed.object({ - error_description: typed.string.optional, - validation_errors: typed.object({}).optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe, + errors: typed.object({}).optional }); -/* Server Error */ + +/* Internal Server Error */ export const apiV1CommentsGetInternalServerError = typed.object({ - error_description: typed.string.optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe }); export type ApiV1CommentsGetFail = { status: "bad_request"; @@ -222,11 +220,11 @@ export type ApiV1CommentsGetFail = { status: "internal_server_error"; error: typed.Get; } | GenericErrors; -export const apiV1CommentsGet = createEffect({ +export const apiV1CommentsGetFx = createEffect({ async handler({ query }) { - const name = "apiV1CommentsGet.body"; + const name = "apiV1CommentsGetFx.body"; const response = await requestFx({ path: "/api/v1/comments", method: "GET", @@ -238,6 +236,7 @@ export const apiV1CommentsGet = createEffect; }; + /* Bad Request */ export const apiV1CommentsPostBadRequest = typed.object({ - error_description: typed.string.optional, - validation_errors: typed.object({}).optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe, + errors: typed.object({}).optional }); + /* Forbidden */ export const apiV1CommentsPostForbidden = typed.object({ - error_description: typed.string.optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe }); -/* Server Error */ + +/* Internal Server Error */ export const apiV1CommentsPostInternalServerError = typed.object({ - error_description: typed.string.optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe }); export type ApiV1CommentsPostFail = { status: "bad_request"; @@ -295,11 +310,11 @@ export type ApiV1CommentsPostFail = { status: "internal_server_error"; error: typed.Get; } | GenericErrors; -export const apiV1CommentsPost = createEffect({ +export const apiV1CommentsPostFx = createEffect({ async handler({ body }) { - const name = "apiV1CommentsPost.body"; + const name = "apiV1CommentsPostFx.body"; const response = await requestFx({ path: "/api/v1/comments", method: "POST", @@ -312,6 +327,7 @@ export const apiV1CommentsPost = createEffect; }; + /* Bad Request */ export const apiV1CommentsParentCommentIdRepliesGetBadRequest = typed.object({ - error_description: typed.string.optional, - validation_errors: typed.object({}).optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe, + errors: typed.object({}).optional }); + /* Not Found */ export const apiV1CommentsParentCommentIdRepliesGetNotFound = typed.object({ - error_description: typed.string.optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe }); -/* Server Error */ + +/* Internal Server Error */ export const apiV1CommentsParentCommentIdRepliesGetInternalServerError = typed.object({ - error_description: typed.string.optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe }); export type ApiV1CommentsParentCommentIdRepliesGetFail = { status: "bad_request"; @@ -372,12 +407,12 @@ export type ApiV1CommentsParentCommentIdRepliesGetFail = { status: "internal_server_error"; error: typed.Get; } | GenericErrors; -export const apiV1CommentsParentCommentIdRepliesGet = createEffect({ +export const apiV1CommentsParentCommentIdRepliesGetFx = createEffect({ async handler({ path, query }) { - const name = "apiV1CommentsParentCommentIdRepliesGet.body"; + const name = "apiV1CommentsParentCommentIdRepliesGetFx.body"; const response = await requestFx({ path: `/api/v1/comments/${path.parentCommentId}/replies`, method: "GET", @@ -390,6 +425,7 @@ export const apiV1CommentsParentCommentIdRepliesGet = createEffect; }; + /* Bad Request */ export const apiV1CommentsParentCommentIdRepliesPostBadRequest = typed.object({ - error_description: typed.string.optional, - validation_errors: typed.object({}).optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe, + errors: typed.object({}).optional }); + /* Forbidden */ export const apiV1CommentsParentCommentIdRepliesPostForbidden = typed.object({ - error_description: typed.string.optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe }); + /* Not Found */ export const apiV1CommentsParentCommentIdRepliesPostNotFound = typed.object({ - error_description: typed.string.optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe }); -/* Server Error */ + +/* Internal Server Error */ export const apiV1CommentsParentCommentIdRepliesPostInternalServerError = typed.object({ - error_description: typed.string.optional + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe }); export type ApiV1CommentsParentCommentIdRepliesPostFail = { status: "bad_request"; @@ -456,12 +513,12 @@ export type ApiV1CommentsParentCommentIdRepliesPostFail = { status: "internal_server_error"; error: typed.Get; } | GenericErrors; -export const apiV1CommentsParentCommentIdRepliesPost = createEffect({ +export const apiV1CommentsParentCommentIdRepliesPostFx = createEffect({ async handler({ body, path }) { - const name = "apiV1CommentsParentCommentIdRepliesPost.body"; + const name = "apiV1CommentsParentCommentIdRepliesPostFx.body"; const response = await requestFx({ path: `/api/v1/comments/${path.parentCommentId}/replies`, method: "POST", @@ -475,6 +532,7 @@ export const apiV1CommentsParentCommentIdRepliesPost = createEffect; }; -export type ApiV1DiscussionsDiscussionIdGetFail = GenericErrors; -export const apiV1DiscussionsDiscussionIdGet = createEffect({ + +/* Not Found */ +export const apiV1DiscussionsDiscussionIdGetNotFound = typed.object({ + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe +}); +export type ApiV1DiscussionsDiscussionIdGetFail = { + status: "not_found"; + error: typed.Get; +} | GenericErrors; +export const apiV1DiscussionsDiscussionIdGetFx = createEffect({ async handler({ path }) { - const name = "apiV1DiscussionsDiscussionIdGet.body"; + const name = "apiV1DiscussionsDiscussionIdGetFx.body"; const response = await requestFx({ path: `/api/v1/discussions/${path.discussionId}`, method: "GET" }); return parseByStatus(name, response, { - 200: ["ok", apiV1DiscussionsDiscussionIdGetOk] + 200: ["ok", apiV1DiscussionsDiscussionIdGetOk], + 404: ["not_found", apiV1DiscussionsDiscussionIdGetNotFound] }); } + }); //#endregion apiV1DiscussionsDiscussionIdGet /* --- */ //#region apiV1DiscussionsGet export type ApiV1DiscussionsGet = { - query?: { + query: { authorId?: string; + offset: number; + count: number; }; }; -/* Success */ -export const apiV1DiscussionsGetOk = typed.array(typed.object({ - id: typed.string.optional, - title: typed.string.optional, - description: typed.string.optional, - author: typed.object({ - id: typed.string.optional, - username: typed.string.optional, - display_name: typed.string.optional, - avatar_url: typed.string.maybe - }).optional -})); + +/* OK */ +export const apiV1DiscussionsGetOk = typed.object({ + total_count: typed.number, + items: typed.array(typed.object({ + id: typed.string, + title: typed.string, + description: typed.string, + author: typed.intersection(typed.object({ + id: typed.string, + username: typed.string, + display_name: typed.string, + avatar_url: typed.string.maybe + })).maybe, + created_at: typed.string + })) +}); export type ApiV1DiscussionsGetDone = { status: "ok"; answer: typed.Get; }; export type ApiV1DiscussionsGetFail = GenericErrors; -export const apiV1DiscussionsGet = createEffect({ +export const apiV1DiscussionsGetFx = createEffect({ async handler({ query }) { - const name = "apiV1DiscussionsGet.body"; + const name = "apiV1DiscussionsGetFx.body"; const response = await requestFx({ path: "/api/v1/discussions", method: "GET", @@ -556,6 +637,7 @@ export const apiV1DiscussionsGet = createEffect; + status: "created"; + answer: typed.Get; }; -export type ApiV1DiscussionsPostFail = GenericErrors; -export const apiV1DiscussionsPost = createEffect({ + +/* Forbidden */ +export const apiV1DiscussionsPostForbidden = typed.object({ + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe +}); +export type ApiV1DiscussionsPostFail = { + status: "forbidden"; + error: typed.Get; +} | GenericErrors; +export const apiV1DiscussionsPostFx = createEffect({ async handler({ body }) { - const name = "apiV1DiscussionsPost.body"; + const name = "apiV1DiscussionsPostFx.body"; const response = await requestFx({ path: "/api/v1/discussions", method: "POST", body }); return parseByStatus(name, response, { - 200: ["ok", apiV1DiscussionsPostOk] + 201: ["created", apiV1DiscussionsPostCreated], + 403: ["forbidden", apiV1DiscussionsPostForbidden] }); } + }); //#endregion apiV1DiscussionsPost +/* --- */ +//#region apiV1AuthenticationSignInPost +export type ApiV1AuthenticationSignInPost = { + body?: { + usernameOrEmail?: string; + password?: string; + }; +}; + +/* OK */ +export const apiV1AuthenticationSignInPostOk = typed.object({ + id: typed.string.optional, + username: typed.string.optional, + display_name: typed.string.optional, + avatar_url: typed.string.maybe +}); +export type ApiV1AuthenticationSignInPostDone = { + status: "ok"; + answer: typed.Get; +}; + +/* Unauthorized */ +export const apiV1AuthenticationSignInPostUnauthorized = typed.object({ + error_description: typed.string.optional +}); + +/* Internal Server Error */ +export const apiV1AuthenticationSignInPostInternalServerError = typed.object({ + error_description: typed.string.optional +}); +export type ApiV1AuthenticationSignInPostFail = { + status: "unauthorized"; + error: typed.Get; +} | { + status: "internal_server_error"; + error: typed.Get; +} | GenericErrors; +export const apiV1AuthenticationSignInPostFx = createEffect({ + async handler({ + body + }) { + const name = "apiV1AuthenticationSignInPostFx.body"; + const response = await requestFx({ + path: "/api/v1/authentication/sign-in", + method: "POST", + body: convertBodyToUrlSearchParams(body) + }); + return parseByStatus(name, response, { + 200: ["ok", apiV1AuthenticationSignInPostOk], + 401: ["unauthorized", apiV1AuthenticationSignInPostUnauthorized], + 500: ["internal_server_error", apiV1AuthenticationSignInPostInternalServerError] + }); + } + +}); +//#endregion apiV1AuthenticationSignInPost + +/* --- */ +//#region apiV1AuthenticationSignOutPost +export type ApiV1AuthenticationSignOutPost = {}; + +/* OK */ +export const apiV1AuthenticationSignOutPostOk = typed.nul; +export type ApiV1AuthenticationSignOutPostDone = { + status: "ok"; + answer: typed.Get; +}; + +/* Unauthorized */ +export const apiV1AuthenticationSignOutPostUnauthorized = typed.object({ + type: typed.string.maybe, + title: typed.string.maybe, + status: typed.number.maybe, + detail: typed.string.maybe, + instance: typed.string.maybe +}); +export type ApiV1AuthenticationSignOutPostFail = { + status: "unauthorized"; + error: typed.Get; +} | GenericErrors; +export const apiV1AuthenticationSignOutPostFx = createEffect({ + async handler() { + const name = "apiV1AuthenticationSignOutPostFx.body"; + const response = await requestFx({ + path: "/api/v1/authentication/sign-out", + method: "POST" + }); + return parseByStatus(name, response, { + 200: ["ok", apiV1AuthenticationSignOutPostOk], + 401: ["unauthorized", apiV1AuthenticationSignOutPostUnauthorized] + }); + } + +}); +//#endregion apiV1AuthenticationSignOutPost + +/* --- */ +//#region apiV1AuthenticationSsoGoogleGet +export type ApiV1AuthenticationSsoGoogleGet = { + query?: { + returnUrl?: string; + }; +}; + +/* Found */ +export const apiV1AuthenticationSsoGoogleGetFound = typed.nul; +export type ApiV1AuthenticationSsoGoogleGetDone = { + status: "found"; + answer: typed.Get; +}; +export type ApiV1AuthenticationSsoGoogleGetFail = GenericErrors; +export const apiV1AuthenticationSsoGoogleGetFx = createEffect({ + async handler({ + query + }) { + const name = "apiV1AuthenticationSsoGoogleGetFx.body"; + const response = await requestFx({ + path: "/api/v1/authentication/sso/google", + method: "GET", + query + }); + return parseByStatus(name, response, { + 302: ["found", apiV1AuthenticationSsoGoogleGetFound] + }); + } + +}); +//#endregion apiV1AuthenticationSsoGoogleGet + +/* --- */ +//#region apiV1AuthenticationSignInGoogleCallbackGet +export type ApiV1AuthenticationSignInGoogleCallbackGet = { + query?: { + code?: string; + state?: string; + }; +}; + +/* OK */ +export const apiV1AuthenticationSignInGoogleCallbackGetOk = typed.object({ + id: typed.string.optional, + username: typed.string.optional, + display_name: typed.string.optional, + avatar_url: typed.string.maybe +}); + +/* Found */ +export const apiV1AuthenticationSignInGoogleCallbackGetFound = typed.nul; +export type ApiV1AuthenticationSignInGoogleCallbackGetDone = { + status: "ok"; + answer: typed.Get; +} | { + status: "found"; + answer: typed.Get; +}; + +/* Bad Request */ +export const apiV1AuthenticationSignInGoogleCallbackGetBadRequest = typed.object({ + error_description: typed.string.optional, + validation_errors: typed.object({}).optional +}); + +/* Unauthorized */ +export const apiV1AuthenticationSignInGoogleCallbackGetUnauthorized = typed.object({ + error_description: typed.string.optional +}); + +/* Internal Server Error */ +export const apiV1AuthenticationSignInGoogleCallbackGetInternalServerError = typed.object({ + error_description: typed.string.optional +}); + +/* Service Unavailable */ +export const apiV1AuthenticationSignInGoogleCallbackGetServiceUnavailable = typed.object({ + error_description: typed.string.optional +}); +export type ApiV1AuthenticationSignInGoogleCallbackGetFail = { + status: "bad_request"; + error: typed.Get; +} | { + status: "unauthorized"; + error: typed.Get; +} | { + status: "internal_server_error"; + error: typed.Get; +} | { + status: "service_unavailable"; + error: typed.Get; +} | GenericErrors; +export const apiV1AuthenticationSignInGoogleCallbackGetFx = createEffect({ + async handler({ + query + }) { + const name = "apiV1AuthenticationSignInGoogleCallbackGetFx.body"; + const response = await requestFx({ + path: "/api/v1/authentication/sign-in-google-callback", + method: "GET", + query + }); + return parseByStatus(name, response, { + 200: ["ok", apiV1AuthenticationSignInGoogleCallbackGetOk], + 302: ["found", apiV1AuthenticationSignInGoogleCallbackGetFound], + 400: ["bad_request", apiV1AuthenticationSignInGoogleCallbackGetBadRequest], + 401: ["unauthorized", apiV1AuthenticationSignInGoogleCallbackGetUnauthorized], + 500: ["internal_server_error", apiV1AuthenticationSignInGoogleCallbackGetInternalServerError], + 503: ["service_unavailable", apiV1AuthenticationSignInGoogleCallbackGetServiceUnavailable] + }); + } + +}); +//#endregion apiV1AuthenticationSignInGoogleCallbackGet + /* --- */ //#region apiV1UsersRegisterPost export type ApiV1UsersRegisterPost = { @@ -608,11 +918,12 @@ export type ApiV1UsersRegisterPost = { username?: string; display_name?: string; email?: string; - password?: string; + password?: string | null; avatar_url?: string | null; }; }; -/* Success */ + +/* OK */ export const apiV1UsersRegisterPostOk = typed.object({ id: typed.string.optional, username: typed.string.optional, @@ -624,16 +935,19 @@ export type ApiV1UsersRegisterPostDone = { status: "ok"; answer: typed.Get; }; + /* Bad Request */ export const apiV1UsersRegisterPostBadRequest = typed.object({ error_description: typed.string.optional, validation_errors: typed.object({}).optional }); + /* Forbidden */ export const apiV1UsersRegisterPostForbidden = typed.object({ error_description: typed.string.optional }); -/* Server Error */ + +/* Internal Server Error */ export const apiV1UsersRegisterPostInternalServerError = typed.object({ error_description: typed.string.optional }); @@ -647,11 +961,11 @@ export type ApiV1UsersRegisterPostFail = { status: "internal_server_error"; error: typed.Get; } | GenericErrors; -export const apiV1UsersRegisterPost = createEffect({ +export const apiV1UsersRegisterPostFx = createEffect({ async handler({ body }) { - const name = "apiV1UsersRegisterPost.body"; + const name = "apiV1UsersRegisterPostFx.body"; const response = await requestFx({ path: "/api/v1/users/register", method: "POST", @@ -664,6 +978,7 @@ export const apiV1UsersRegisterPost = createEffect; }; + /* Not Found */ export const apiV1UsersUserIdGetNotFound = typed.object({ error_description: typed.string.optional }); -/* Server Error */ + +/* Internal Server Error */ export const apiV1UsersUserIdGetInternalServerError = typed.object({ error_description: typed.string.optional }); @@ -700,11 +1018,11 @@ export type ApiV1UsersUserIdGetFail = { status: "internal_server_error"; error: typed.Get; } | GenericErrors; -export const apiV1UsersUserIdGet = createEffect({ +export const apiV1UsersUserIdGetFx = createEffect({ async handler({ path }) { - const name = "apiV1UsersUserIdGet.body"; + const name = "apiV1UsersUserIdGetFx.body"; const response = await requestFx({ path: `/api/v1/users/${path.userId}`, method: "GET" @@ -715,6 +1033,7 @@ export const apiV1UsersUserIdGet = createEffect; }; + /* Bad Request */ export const apiV1UsersUserIdPutBadRequest = typed.object({ error_description: typed.string.optional, validation_errors: typed.object({}).optional }); + /* Forbidden */ export const apiV1UsersUserIdPutForbidden = typed.object({ error_description: typed.string.optional }); + /* Not Found */ export const apiV1UsersUserIdPutNotFound = typed.object({ error_description: typed.string.optional }); -/* Server Error */ + +/* Internal Server Error */ export const apiV1UsersUserIdPutInternalServerError = typed.object({ error_description: typed.string.optional }); @@ -775,12 +1099,12 @@ export type ApiV1UsersUserIdPutFail = { status: "internal_server_error"; error: typed.Get; } | GenericErrors; -export const apiV1UsersUserIdPut = createEffect({ +export const apiV1UsersUserIdPutFx = createEffect({ async handler({ body, path }) { - const name = "apiV1UsersUserIdPut.body"; + const name = "apiV1UsersUserIdPutFx.body"; const response = await requestFx({ path: `/api/v1/users/${path.userId}`, method: "PUT", @@ -794,6 +1118,7 @@ export const apiV1UsersUserIdPut = createEffect; }; + /* Forbidden */ export const apiV1UsersUserIdDeleteForbidden = typed.object({ error_description: typed.string.optional }); + /* Not Found */ export const apiV1UsersUserIdDeleteNotFound = typed.object({ error_description: typed.string.optional }); -/* Server Error */ + +/* Internal Server Error */ export const apiV1UsersUserIdDeleteInternalServerError = typed.object({ error_description: typed.string.optional }); @@ -838,11 +1167,11 @@ export type ApiV1UsersUserIdDeleteFail = { status: "internal_server_error"; error: typed.Get; } | GenericErrors; -export const apiV1UsersUserIdDelete = createEffect({ +export const apiV1UsersUserIdDeleteFx = createEffect({ async handler({ path }) { - const name = "apiV1UsersUserIdDelete.body"; + const name = "apiV1UsersUserIdDeleteFx.body"; const response = await requestFx({ path: `/api/v1/users/${path.userId}`, method: "DELETE" @@ -854,9 +1183,59 @@ export const apiV1UsersUserIdDelete = createEffect; +}; + +/* Not Found */ +export const apiV1UsersSelfGetNotFound = typed.object({ + error_description: typed.string.optional +}); + +/* Internal Server Error */ +export const apiV1UsersSelfGetInternalServerError = typed.object({ + error_description: typed.string.optional +}); +export type ApiV1UsersSelfGetFail = { + status: "not_found"; + error: typed.Get; +} | { + status: "internal_server_error"; + error: typed.Get; +} | GenericErrors; +export const apiV1UsersSelfGetFx = createEffect({ + async handler() { + const name = "apiV1UsersSelfGetFx.body"; + const response = await requestFx({ + path: "/api/v1/users/self", + method: "GET" + }); + return parseByStatus(name, response, { + 200: ["ok", apiV1UsersSelfGetOk], + 404: ["not_found", apiV1UsersSelfGetNotFound], + 500: ["internal_server_error", apiV1UsersSelfGetInternalServerError] + }); + } + +}); +//#endregion apiV1UsersSelfGet + /* --- */ //#region apiV1UsersResolveGet export type ApiV1UsersResolveGet = { @@ -864,7 +1243,8 @@ export type ApiV1UsersResolveGet = { username?: string; }; }; -/* Success */ + +/* OK */ export const apiV1UsersResolveGetOk = typed.object({ id: typed.string.optional, username: typed.string.optional, @@ -875,16 +1255,19 @@ export type ApiV1UsersResolveGetDone = { status: "ok"; answer: typed.Get; }; + /* Bad Request */ export const apiV1UsersResolveGetBadRequest = typed.object({ error_description: typed.string.optional, validation_errors: typed.object({}).optional }); + /* Not Found */ export const apiV1UsersResolveGetNotFound = typed.object({ error_description: typed.string.optional }); -/* Server Error */ + +/* Internal Server Error */ export const apiV1UsersResolveGetInternalServerError = typed.object({ error_description: typed.string.optional }); @@ -898,11 +1281,11 @@ export type ApiV1UsersResolveGetFail = { status: "internal_server_error"; error: typed.Get; } | GenericErrors; -export const apiV1UsersResolveGet = createEffect({ +export const apiV1UsersResolveGetFx = createEffect({ async handler({ query }) { - const name = "apiV1UsersResolveGet.body"; + const name = "apiV1UsersResolveGetFx.body"; const response = await requestFx({ path: "/api/v1/users/resolve", method: "GET", @@ -915,6 +1298,7 @@ export const apiV1UsersResolveGet = createEffect((request) => { + if (request.path.includes('/sso/')) { + const url = new URL(request.path, API_URL); + + if (request.query) { + url.search = new URLSearchParams(request.query as Record).toString(); + } + + window.location.href = url.href; + + return Promise.resolve({status: 302, body: null}); + } + return localApi({ method: request.method, url: request.path, diff --git a/src/shared/session/index.ts b/src/shared/session/index.ts index 3fe4e52..d65cd00 100644 --- a/src/shared/session/index.ts +++ b/src/shared/session/index.ts @@ -1,15 +1,22 @@ import * as typed from 'typed-contracts'; -import {chainRoute, redirect, RouteInstance, RouteParams, RouteParamsAndQuery} from 'atomic-router'; +import { + chainRoute, + redirect, + RouteInstance, + RouteParams, + RouteParamsAndQuery, + RouteQuery, +} from 'atomic-router'; import {attach, createEvent, createStore, Effect, sample} from 'effector'; import {persist} from 'effector-storage/local'; -import {decodeToken} from 'react-jwt'; -import {ApiV1UsersUserIdGet, apiV1UsersUserIdGet, apiV1UsersUserIdGetOk} from '~/shared/api'; -import {logoutFx} from '~/shared/api/auth'; -import {JwtPayload} from '~/shared/api/types'; -import {LOCAL_STORAGE_ACCESS_TOKEN_KEY} from '~/shared/config'; +import { + apiV1AuthenticationSignOutPostFx, + apiV1UsersSelfGetFx, + apiV1UsersUserIdGetOk, +} from '~/shared/api'; -import {routes} from '../routes'; +import {routes, routesMap} from '../routes'; enum AuthStatus { Initial = 0, @@ -19,24 +26,7 @@ enum AuthStatus { } export const sessionRequestFx = attach({ - effect: apiV1UsersUserIdGet, - mapParams: (): ApiV1UsersUserIdGet => { - let userId: string = ''; - - const accessToken = localStorage.getItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY); - if (accessToken) { - const jwtPayload = decodeToken(accessToken); - if (jwtPayload) { - userId = jwtPayload.sub; - } - } - - return { - path: { - userId, - }, - }; - }, + effect: apiV1UsersSelfGetFx, }); export const $user = createStore | null>(null); @@ -55,11 +45,11 @@ $authenticationStatus.on(sessionRequestFx.doneData, () => AuthStatus.Authenticat $authenticationStatus.on(sessionRequestFx.fail, () => AuthStatus.Anonymous); $user.on(sessionRequestFx.fail, () => null); -$user.on(logoutFx.done, () => null); -$authenticationStatus.on(logoutFx.done, () => AuthStatus.Anonymous); +$user.on(apiV1AuthenticationSignOutPostFx.done, () => null); +$authenticationStatus.on(apiV1AuthenticationSignOutPostFx.done, () => AuthStatus.Anonymous); redirect({ - clock: logoutFx.done, + clock: apiV1AuthenticationSignOutPostFx.done, route: routes.auth.signIn, }); @@ -106,8 +96,20 @@ export function chainAuthorized( if (otherwise) { sample({ clock: sessionReceivedAnonymous, + fn: () => { + const routeMap = routesMap.find((r) => r.route === route); + + if (routeMap) { + const returnUrl = new URL(routeMap.path, window.location.origin).href; + return { + query: { + returnUrl, + }, + }; + } + }, // @ts-ignore - target: otherwise as Event, + target: otherwise as Event<{query: RouteQuery} | void>, }); }