);
diff --git a/src/pages/index.ts b/frontend/src/pages/index.ts
similarity index 100%
rename from src/pages/index.ts
rename to frontend/src/pages/index.ts
diff --git a/src/pages/notfound/Page.module.scss b/frontend/src/pages/notfound/Page.module.scss
similarity index 100%
rename from src/pages/notfound/Page.module.scss
rename to frontend/src/pages/notfound/Page.module.scss
diff --git a/src/pages/notfound/model/page.model.ts b/frontend/src/pages/notfound/model/page.model.ts
similarity index 100%
rename from src/pages/notfound/model/page.model.ts
rename to frontend/src/pages/notfound/model/page.model.ts
diff --git a/src/pages/notfound/page.tsx b/frontend/src/pages/notfound/page.tsx
similarity index 73%
rename from src/pages/notfound/page.tsx
rename to frontend/src/pages/notfound/page.tsx
index d8d3e771..e760bfb3 100644
--- a/src/pages/notfound/page.tsx
+++ b/frontend/src/pages/notfound/page.tsx
@@ -4,20 +4,20 @@ import { useUnit } from 'effector-react';
import { useTranslation } from 'react-i18next';
import { redirectBack } from './model/page.model';
-import st from './Page.module.scss';
+import st from './page.module.scss';
function Page() {
const redirect = useUnit(redirectBack);
const { t } = useTranslation('notfound', { keyPrefix: 'baseOtherwise' });
return (
-
+
{t('header')}
-
+
{t('headerGroup')}
-
diff --git a/src/pages/start/page.module.scss b/frontend/src/pages/start/page.module.scss
similarity index 100%
rename from src/pages/start/page.module.scss
rename to frontend/src/pages/start/page.module.scss
diff --git a/src/pages/start/page.tsx b/frontend/src/pages/start/page.tsx
similarity index 56%
rename from src/pages/start/page.tsx
rename to frontend/src/pages/start/page.tsx
index c196fd34..8e8d00a8 100644
--- a/src/pages/start/page.tsx
+++ b/frontend/src/pages/start/page.tsx
@@ -1,12 +1,12 @@
import { Loader } from '@shared/ui/Loader';
-import st from './Page.module.scss';
+import st from './page.module.scss';
function Page() {
return (
-
+
{'Flippo'}
-
+
);
}
diff --git a/src/settings/i18next/i18next.config.ts b/frontend/src/settings/i18next/i18next.config.ts
similarity index 92%
rename from src/settings/i18next/i18next.config.ts
rename to frontend/src/settings/i18next/i18next.config.ts
index 7a700218..56fe1eef 100644
--- a/src/settings/i18next/i18next.config.ts
+++ b/frontend/src/settings/i18next/i18next.config.ts
@@ -1,6 +1,8 @@
-import { createI18nextIntegration, type I18nextIntegration } from '@withease/i18next';
+import type { I18nextIntegration } from '@withease/i18next';
+import type { i18n } from 'i18next';
+import { createI18nextIntegration } from '@withease/i18next';
import { createEvent, createStore } from 'effector';
-import i18next, { type i18n } from 'i18next';
+import i18next from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import { HMRPlugin } from 'i18next-hmr/plugin';
import HttpBackend from 'i18next-http-backend';
diff --git a/src/settings/i18next/i18next.constants.ts b/frontend/src/settings/i18next/i18next.constants.ts
similarity index 100%
rename from src/settings/i18next/i18next.constants.ts
rename to frontend/src/settings/i18next/i18next.constants.ts
diff --git a/src/settings/i18next/i18next.d.ts b/frontend/src/settings/i18next/i18next.d.ts
similarity index 84%
rename from src/settings/i18next/i18next.d.ts
rename to frontend/src/settings/i18next/i18next.d.ts
index 3defa78f..1b4c7a09 100644
--- a/src/settings/i18next/i18next.d.ts
+++ b/frontend/src/settings/i18next/i18next.d.ts
@@ -1,5 +1,9 @@
+import type { Effect, Store } from 'effector';
+import type { Event as Event_2 } from 'effector';
+import type { i18n, TFunction } from 'i18next';
import type auth from '../../../locales/en/auth.json';
import type header from '../../../locales/en/header.json';
+
import type modal from '../../../locales/en/modal.json';
import type notfound from '../../../locales/en/notfound.json';
import type translation from '../../../locales/en/translation.json';
@@ -19,15 +23,11 @@ type TResource = {
};
declare module 'i18next' {
- interface CustomTypeOptions {
+ type CustomTypeOptions = {
resources: TResource;
- }
+ };
}
-import { type Effect, type Store } from 'effector';
-import { type Event as Event_2 } from 'effector';
-import { type i18n, type TFunction } from 'i18next';
-
declare module '@withease/i18next' {
export type I18nextIntegration = {
$instance: Store
;
@@ -48,8 +48,8 @@ declare module '@withease/i18next' {
res: string;
};
- export interface Translated {
+ export type Translated = {
(key: string, variables?: Record>): Store;
(parts: TemplateStringsArray, ...stores: Store[]): Store;
- }
+ };
}
diff --git a/src/settings/i18next/index.ts b/frontend/src/settings/i18next/index.ts
similarity index 100%
rename from src/settings/i18next/index.ts
rename to frontend/src/settings/i18next/index.ts
diff --git a/src/settings/routing/index.ts b/frontend/src/settings/routing/index.ts
similarity index 65%
rename from src/settings/routing/index.ts
rename to frontend/src/settings/routing/index.ts
index 4e9428d0..6ec2802b 100644
--- a/src/settings/routing/index.ts
+++ b/frontend/src/settings/routing/index.ts
@@ -1,5 +1,6 @@
+import type { UnmappedRouteObject } from 'atomic-router';
import { chainAnonymous, chainAuthorized, chainOptionalAuthorization } from '@settings/session';
-import { createHistoryRouter, createRoute, type UnmappedRouteObject } from 'atomic-router';
+import { createHistoryRouter, createRoute } from 'atomic-router';
import { createEvent, sample } from 'effector';
import { createBrowserHistory } from 'history';
@@ -7,6 +8,14 @@ export type TSettingsRouteParams = {
userId?: string;
};
+export type TFolderRouteParams = {
+ folderId: string;
+};
+
+export type TSetRouteParams = {
+ setId: string;
+};
+
export const initRouter = createEvent();
export const mainRoute = createRoute();
@@ -15,12 +24,16 @@ export const callbackRoute = createRoute();
export const collectionRoute = createRoute();
export const communityRoute = createRoute();
export const settingsRoute = createRoute();
+export const folderRoute = createRoute();
+export const setRoute = createRoute();
const ROUTES: UnmappedRouteObject[] = [
{ path: '/', route: mainRoute },
{ path: '/auth', route: authRoute },
{ path: '/auth/callback', route: callbackRoute },
{ path: '/collection', route: collectionRoute },
+ { path: '/folders/:folderId', route: folderRoute },
+ { path: '/sets/:setId', route: setRoute },
{ path: '/community', route: communityRoute },
{ path: '/settings/:userId', route: settingsRoute }
];
@@ -32,6 +45,8 @@ sample({ clock: initRouter, fn: () => createBrowserHistory(), target: router.set
export const mainOptionalRoute = chainOptionalAuthorization(mainRoute);
export const collectionOptionalRoute = chainOptionalAuthorization(collectionRoute);
export const communityOptionalRoute = chainOptionalAuthorization(communityRoute);
-export const authAnonymousRoute = chainAnonymous(authRoute); //, { otherwise: router.back });
-export const callbackAnonymousRoute = chainAnonymous(callbackRoute); //, { otherwise: router.back });
-export const settingsAuthorizedRoute = chainAuthorized(settingsRoute); //, { otherwise: mainRoute.open });
+export const folderOptionalRoute = chainOptionalAuthorization(folderRoute);
+export const setOptionalRoute = chainOptionalAuthorization(setRoute);
+export const authAnonymousRoute = chainAnonymous(authRoute); // , { otherwise: router.back });
+export const callbackAnonymousRoute = chainAnonymous(callbackRoute); // , { otherwise: router.back });
+export const settingsAuthorizedRoute = chainAuthorized(settingsRoute); // , { otherwise: mainRoute.open });
diff --git a/src/settings/session/index.ts b/frontend/src/settings/session/index.ts
similarity index 100%
rename from src/settings/session/index.ts
rename to frontend/src/settings/session/index.ts
diff --git a/frontend/src/settings/session/session.api.ts b/frontend/src/settings/session/session.api.ts
new file mode 100644
index 00000000..e99fcea8
--- /dev/null
+++ b/frontend/src/settings/session/session.api.ts
@@ -0,0 +1,41 @@
+import type { AxiosError } from 'axios';
+import { createMutation } from '@farfetched/core';
+import { ENV } from '@shared/env';
+import axios from 'axios';
+import { createEffect } from 'effector';
+
+const api = axios.create({
+ baseURL: `${ENV.API_BASE_URL}/auth`,
+ timeout: 10000,
+ validateStatus: status => status >= 200 && status < 300
+});
+
+export function createSessionAuthMut() {
+ return createMutation({
+ effect: createEffect(async () => {
+ await api.get('', {
+ withCredentials: true
+ });
+ })
+ });
+}
+
+export function createSessionRefreshMut() {
+ return createMutation({
+ effect: createEffect(async () => {
+ await api.get('/refresh_token/refresh', {
+ withCredentials: true
+ });
+ })
+ });
+}
+
+export function createSessionSignOutMut() {
+ return createMutation({
+ effect: createEffect(async () => {
+ await api.post('/refresh_token/signout', {
+ withCredentials: true
+ });
+ })
+ });
+}
diff --git a/frontend/src/settings/session/session.model.ts b/frontend/src/settings/session/session.model.ts
new file mode 100644
index 00000000..1ed1876b
--- /dev/null
+++ b/frontend/src/settings/session/session.model.ts
@@ -0,0 +1,364 @@
+import type { Mutation, Query } from '@farfetched/core';
+import type { TTranslationOptions } from '@widgets/ToastNotification';
+import type { RouteInstance, RouteParams, RouteParamsAndQuery } from 'atomic-router';
+import type { Effect, Event, EventCallable } from 'effector';
+import type { TSession } from './session.schema';
+import { retry } from '@farfetched/core';
+import { SurrealError } from '@settings/surreal';
+import * as dbApi from '@settings/surreal';
+import { getErrorStatus } from '@shared/api';
+import { displayRequestError } from '@widgets/ToastNotification';
+import { chainRoute } from 'atomic-router';
+import {
+ attach,
+ createEffect,
+ createEvent,
+ createStore,
+
+ is,
+ sample,
+ split
+} from 'effector';
+
+import { and, not } from 'patronum';
+import { createSessionAuthMut, createSessionRefreshMut, createSessionSignOutMut } from './session.api';
+import { SessionSchema } from './session.schema';
+
+type RemoteOperation = Mutation | Query;
+type RetryConfig = Parameters>['1'];
+type Otherwise = Exclude['otherwise'], undefined>;
+type FailInfo = Exclude>['0'], undefined>;
+
+enum AuthStatus {
+ Initial = 0,
+ Pending,
+ Anonymous,
+ Authenticated
+}
+
+// #region session
+const sessionAuthMut = createSessionAuthMut();
+const sessionRefreshMut = createSessionRefreshMut();
+const sessionSignOutMut = createSessionSignOutMut();
+
+const dbConnectFx = attach({ effect: dbApi.dbConnectFx });
+const dbAuthenticateFx = attach({ effect: dbApi.dbAuthenticateFx });
+const dbInvalidateFx = attach({ effect: dbApi.dbInvalidateFx });
+const getAuthDbFx = attach({ effect: dbApi.getAuthDbFx });
+
+export const $session = createStore(null);
+export const sessionValidateFx = createEffect((session) => {
+ const validSession = SessionSchema.parse(session);
+
+ return validSession;
+});
+export const sessionChangedFx = createEffect(async () => {
+ const db = await getAuthDbFx();
+ const info = (await db.info()) as Exclude>, undefined>;
+
+ const pickProperties = new Set(SessionSchema.keyof().options as string[]);
+ const session = await sessionValidateFx(
+ Object.fromEntries(Object.entries(info).filter(entry => pickProperties.has(entry[0])))
+ );
+
+ return session;
+});
+
+export const sessionAuth = createEvent();
+export const sessionSignOut = createEvent();
+export const sessionAuthenticateDatabase = createEvent();
+
+const sessionReset = createEvent();
+
+const $authenticationStatus = createStore(AuthStatus.Initial);
+
+$session.reset(sessionReset);
+
+// change authentication status
+$authenticationStatus.on(sessionAuthMut.started, (status) => {
+ if (status === AuthStatus.Initial)
+ return AuthStatus.Pending;
+ return status;
+});
+$authenticationStatus.on(dbAuthenticateFx.done, () => AuthStatus.Authenticated);
+$authenticationStatus.on(
+ [sessionAuthMut.finished.failure, dbAuthenticateFx.fail, sessionSignOutMut.start],
+ () => AuthStatus.Anonymous
+);
+
+// request token for db
+sample({
+ clock: sessionAuth,
+ filter: not(sessionAuthMut.$pending),
+ target: sessionAuthMut.start
+});
+
+// success get token -> authenticate db
+sample({
+ clock: sessionAuthMut.finished.success,
+ target: sessionAuthenticateDatabase
+});
+
+sample({
+ clock: sessionAuthenticateDatabase,
+ filter: not(dbAuthenticateFx.pending),
+ target: dbAuthenticateFx
+});
+
+// success get authenticate db -> extract info on user
+sample({
+ clock: dbAuthenticateFx.done,
+ target: sessionChangedFx
+});
+
+// success get extracted info on user -> setting session data
+sample({
+ clock: sessionChangedFx.doneData,
+ target: $session
+});
+
+// success refresh -> authenticate session
+sample({ clock: sessionRefreshMut.finished.success, target: sessionAuth });
+
+// failure get token -> refresh auth token
+sample({
+ clock: sessionAuthMut.finished.failure,
+ filter: not(sessionRefreshMut.$pending),
+ target: sessionRefreshMut.start
+});
+
+retry(sessionRefreshMut, {
+ delay: 0,
+ filter: (data) => {
+ if (['400', '401'].includes(getErrorStatus(data.error)))
+ return false;
+
+ return true;
+ },
+ otherwise: sessionSignOut as unknown as EventCallable>,
+ times: 2
+});
+
+// #region retry logic for authenticate in db
+const RETRY_ATTEMPTS_ERR_OFFLINE = 2;
+const RETRY_ATTEMPTS_ERR_TOKEN_MISSING = 2;
+
+const $retryOffline = createStore(0);
+const $retryTokenMissing = createStore(0);
+
+const retryOfflineFx = attach({
+ effect: createEffect(async (attempt) => {
+ if (attempt === RETRY_ATTEMPTS_ERR_OFFLINE)
+ throw new Error('The attempts to connect to the database have ended.');
+
+ await dbConnectFx();
+ }),
+ source: $retryOffline
+});
+
+const retryTokenMissingFx = attach({
+ effect: createEffect((attempt) => {
+ if (attempt === RETRY_ATTEMPTS_ERR_TOKEN_MISSING)
+ throw new Error('The attempts to authenticate the connection to the database have ended.');
+ }),
+ source: $retryTokenMissing
+});
+
+const displayErrorAuthenticateDb = createEvent();
+
+sample({
+ clock: displayErrorAuthenticateDb,
+ fn: (): TTranslationOptions => ['error.500'],
+ target: displayRequestError
+});
+
+split({
+ source: dbAuthenticateFx.failData,
+ match: (error) => {
+ if (error instanceof SurrealError)
+ return error.code;
+
+ return '__';
+ },
+ cases: {
+ ERR_OFFLINE: retryOfflineFx,
+ ERR_TOKEN_MISSING: retryTokenMissingFx,
+ __: displayErrorAuthenticateDb
+ }
+});
+
+sample({
+ clock: retryOfflineFx.done,
+ target: sessionAuthenticateDatabase
+});
+
+sample({
+ clock: retryTokenMissingFx.done,
+ target: sessionAuth
+});
+
+sample({
+ clock: [retryOfflineFx.fail, retryTokenMissingFx.fail],
+ target: displayErrorAuthenticateDb
+});
+// #endregion
+
+// sign out of account
+sample({
+ clock: sessionSignOut,
+ filter: and($session, not(sessionSignOutMut.$pending)),
+ target: [sessionSignOutMut.start, sessionReset, dbInvalidateFx]
+});
+
+sample({
+ clock: sessionSignOutMut.finished.failure,
+ target: createEffect(() => console.warn('Server side error.'))
+});
+// #endregion
+
+// #region chain routes of authorization
+type ChainParams = {
+ otherwise?: Effect | Event | EventCallable;
+};
+
+export function chainAuthorized(
+ route: RouteInstance,
+ { otherwise }: ChainParams = {}
+): RouteInstance {
+ const sessionCheckStarted = createEvent>();
+ const sessionReceivedAnonymous = createEvent>();
+
+ const alreadyAuthenticated = sample({
+ clock: sessionCheckStarted,
+ source: $authenticationStatus,
+ filter: status => status === AuthStatus.Authenticated
+ });
+
+ const alreadyAnonymous = sample({
+ clock: sessionCheckStarted,
+ source: $authenticationStatus,
+ filter: status => status === AuthStatus.Anonymous
+ });
+
+ sample({
+ clock: sessionCheckStarted,
+ source: $authenticationStatus,
+ filter: status => status === AuthStatus.Initial,
+ target: sessionAuthMut.start
+ });
+
+ sample({
+ clock: [alreadyAnonymous, sessionAuthMut.finished.failure],
+ source: { params: route.$params, query: route.$query },
+ filter: route.$isOpened,
+ target: sessionReceivedAnonymous
+ });
+
+ if (otherwise) {
+ split({
+ source: sessionReceivedAnonymous,
+ match: {
+ effect: () => is.effect(otherwise),
+ event: () => is.event(otherwise)
+ },
+ cases: {
+ effect: otherwise,
+ event: otherwise
+ }
+ });
+ }
+
+ return chainRoute({
+ route,
+ beforeOpen: sessionCheckStarted,
+ openOn: [alreadyAuthenticated, sessionAuthMut.finished.success],
+ cancelOn: sessionReceivedAnonymous
+ });
+}
+
+export function chainAnonymous(
+ route: RouteInstance,
+ { otherwise }: ChainParams = {}
+): RouteInstance {
+ const sessionCheckStarted = createEvent>();
+ const sessionReceivedAuthenticated = createEvent>();
+
+ const alreadyAuthenticated = sample({
+ clock: sessionCheckStarted,
+ source: $authenticationStatus,
+ filter: status => status === AuthStatus.Authenticated
+ });
+
+ const alreadyAnonymous = sample({
+ clock: sessionCheckStarted,
+ source: $authenticationStatus,
+ filter: status => status === AuthStatus.Anonymous
+ });
+
+ sample({
+ clock: sessionCheckStarted,
+ source: $authenticationStatus,
+ filter: status => status === AuthStatus.Initial,
+ target: sessionAuthMut.start
+ });
+
+ sample({
+ clock: [alreadyAuthenticated, sessionAuthMut.finished.success],
+ source: { params: route.$params, query: route.$query },
+ filter: route.$isOpened,
+ target: sessionReceivedAuthenticated
+ });
+
+ if (otherwise) {
+ split({
+ source: sessionReceivedAuthenticated,
+ match: {
+ effect: () => is.effect(otherwise),
+ event: () => is.event(otherwise)
+ },
+ cases: {
+ effect: otherwise,
+ event: otherwise
+ }
+ });
+ }
+
+ return chainRoute({
+ route,
+ beforeOpen: sessionCheckStarted,
+ openOn: [alreadyAnonymous, sessionAuthMut.finished.failure],
+ cancelOn: sessionReceivedAuthenticated
+ });
+}
+
+export function chainOptionalAuthorization(
+ route: RouteInstance
+): RouteInstance {
+ const sessionCheckStarted = createEvent>();
+
+ const alreadyAuthenticated = sample({
+ clock: sessionCheckStarted,
+ source: $authenticationStatus,
+ filter: status => status === AuthStatus.Authenticated
+ });
+
+ const alreadyAnonymous = sample({
+ clock: sessionCheckStarted,
+ source: $authenticationStatus,
+ filter: status => status === AuthStatus.Anonymous
+ });
+
+ sample({
+ clock: sessionCheckStarted,
+ source: $authenticationStatus,
+ filter: status => status === AuthStatus.Initial,
+ target: sessionAuthMut.start
+ });
+
+ return chainRoute({
+ route,
+ beforeOpen: sessionCheckStarted,
+ openOn: [alreadyAnonymous, alreadyAuthenticated, sessionAuthMut.finished.finally]
+ });
+}
+// #endregion
diff --git a/src/settings/session/session.schema.ts b/frontend/src/settings/session/session.schema.ts
similarity index 83%
rename from src/settings/session/session.schema.ts
rename to frontend/src/settings/session/session.schema.ts
index f05356e5..ec6cbde7 100644
--- a/src/settings/session/session.schema.ts
+++ b/frontend/src/settings/session/session.schema.ts
@@ -1,4 +1,4 @@
-import { record } from '@shared/schemas';
+import { RecordId } from 'surrealdb';
import { z } from 'zod';
const SessionSchema = z.object({
@@ -7,7 +7,7 @@ const SessionSchema = z.object({
name: z.string().optional(),
role: z.union([z.literal('user'), z.literal('admin'), z.literal('premium')]),
surname: z.string().optional(),
- userId: record('user'),
+ userId: z.instanceof(RecordId<'user'>),
username: z.string().optional()
});
diff --git a/src/settings/styles/fonts/vela-sans/COPYRIGHT.txt b/frontend/src/settings/styles/fonts/vela-sans/COPYRIGHT.txt
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/COPYRIGHT.txt
rename to frontend/src/settings/styles/fonts/vela-sans/COPYRIGHT.txt
diff --git a/src/settings/styles/fonts/vela-sans/LICENSE.txt b/frontend/src/settings/styles/fonts/vela-sans/LICENSE.txt
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/LICENSE.txt
rename to frontend/src/settings/styles/fonts/vela-sans/LICENSE.txt
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-Bold.ttf b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-Bold.ttf
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-Bold.ttf
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-Bold.ttf
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-Bold.woff b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-Bold.woff
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-Bold.woff
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-Bold.woff
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-Bold.woff2 b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-Bold.woff2
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-Bold.woff2
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-Bold.woff2
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-ExtraBold.ttf b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-ExtraBold.ttf
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-ExtraBold.ttf
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-ExtraBold.ttf
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-ExtraBold.woff b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-ExtraBold.woff
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-ExtraBold.woff
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-ExtraBold.woff
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-ExtraBold.woff2 b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-ExtraBold.woff2
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-ExtraBold.woff2
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-ExtraBold.woff2
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-ExtraLight.ttf b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-ExtraLight.ttf
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-ExtraLight.ttf
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-ExtraLight.ttf
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-ExtraLight.woff b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-ExtraLight.woff
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-ExtraLight.woff
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-ExtraLight.woff
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-ExtraLight.woff2 b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-ExtraLight.woff2
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-ExtraLight.woff2
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-ExtraLight.woff2
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-GX.ttf b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-GX.ttf
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-GX.ttf
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-GX.ttf
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-Light.ttf b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-Light.ttf
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-Light.ttf
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-Light.ttf
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-Light.woff b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-Light.woff
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-Light.woff
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-Light.woff
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-Light.woff2 b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-Light.woff2
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-Light.woff2
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-Light.woff2
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-Medium.ttf b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-Medium.ttf
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-Medium.ttf
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-Medium.ttf
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-Medium.woff b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-Medium.woff
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-Medium.woff
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-Medium.woff
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-Medium.woff2 b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-Medium.woff2
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-Medium.woff2
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-Medium.woff2
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-Regular.ttf b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-Regular.ttf
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-Regular.ttf
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-Regular.ttf
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-Regular.woff b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-Regular.woff
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-Regular.woff
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-Regular.woff
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-Regular.woff2 b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-Regular.woff2
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-Regular.woff2
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-Regular.woff2
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-SemiBold.ttf b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-SemiBold.ttf
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-SemiBold.ttf
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-SemiBold.ttf
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-SemiBold.woff b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-SemiBold.woff
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-SemiBold.woff
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-SemiBold.woff
diff --git a/src/settings/styles/fonts/vela-sans/VelaSans-SemiBold.woff2 b/frontend/src/settings/styles/fonts/vela-sans/VelaSans-SemiBold.woff2
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSans-SemiBold.woff2
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSans-SemiBold.woff2
diff --git a/src/settings/styles/fonts/vela-sans/VelaSansGX-ExtraLight.woff b/frontend/src/settings/styles/fonts/vela-sans/VelaSansGX-ExtraLight.woff
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSansGX-ExtraLight.woff
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSansGX-ExtraLight.woff
diff --git a/src/settings/styles/fonts/vela-sans/VelaSansGX-ExtraLight.woff2 b/frontend/src/settings/styles/fonts/vela-sans/VelaSansGX-ExtraLight.woff2
similarity index 100%
rename from src/settings/styles/fonts/vela-sans/VelaSansGX-ExtraLight.woff2
rename to frontend/src/settings/styles/fonts/vela-sans/VelaSansGX-ExtraLight.woff2
diff --git a/src/settings/styles/functions/_hexToRgb.scss b/frontend/src/settings/styles/functions/_hexToRgb.scss
similarity index 100%
rename from src/settings/styles/functions/_hexToRgb.scss
rename to frontend/src/settings/styles/functions/_hexToRgb.scss
diff --git a/src/settings/styles/global.scss b/frontend/src/settings/styles/global.scss
similarity index 99%
rename from src/settings/styles/global.scss
rename to frontend/src/settings/styles/global.scss
index 39370e23..85e82c7c 100644
--- a/src/settings/styles/global.scss
+++ b/frontend/src/settings/styles/global.scss
@@ -1,6 +1,6 @@
@use './variables/_colors.scss';
@use './variables/_borders.scss';
-@use './variables/zIndex';
+@use './variables/_zIndex';
@font-face {
font-family: 'Vela Sans ExtBd';
diff --git a/src/settings/styles/mixins/_effect.scss b/frontend/src/settings/styles/mixins/_effect.scss
similarity index 100%
rename from src/settings/styles/mixins/_effect.scss
rename to frontend/src/settings/styles/mixins/_effect.scss
diff --git a/src/settings/styles/mixins/_flex.scss b/frontend/src/settings/styles/mixins/_flex.scss
similarity index 100%
rename from src/settings/styles/mixins/_flex.scss
rename to frontend/src/settings/styles/mixins/_flex.scss
diff --git a/src/settings/styles/mixins/_font.scss b/frontend/src/settings/styles/mixins/_font.scss
similarity index 97%
rename from src/settings/styles/mixins/_font.scss
rename to frontend/src/settings/styles/mixins/_font.scss
index d26bb716..7a581f5b 100644
--- a/src/settings/styles/mixins/_font.scss
+++ b/frontend/src/settings/styles/mixins/_font.scss
@@ -1,6 +1,6 @@
-$default: "default";
-$stronger: "stronger";
-$weaker: "weaker";
+$default: 'default';
+$stronger: 'stronger';
+$weaker: 'weaker';
@mixin display-1($variant) {
line-height: 150%;
diff --git a/src/settings/styles/variables/_borders.scss b/frontend/src/settings/styles/variables/_borders.scss
similarity index 100%
rename from src/settings/styles/variables/_borders.scss
rename to frontend/src/settings/styles/variables/_borders.scss
diff --git a/src/settings/styles/variables/_colors.scss b/frontend/src/settings/styles/variables/_colors.scss
similarity index 100%
rename from src/settings/styles/variables/_colors.scss
rename to frontend/src/settings/styles/variables/_colors.scss
diff --git a/src/settings/styles/variables/_zIndex.scss b/frontend/src/settings/styles/variables/_zIndex.scss
similarity index 100%
rename from src/settings/styles/variables/_zIndex.scss
rename to frontend/src/settings/styles/variables/_zIndex.scss
diff --git a/src/settings/surreal/index.ts b/frontend/src/settings/surreal/index.ts
similarity index 71%
rename from src/settings/surreal/index.ts
rename to frontend/src/settings/surreal/index.ts
index 963cd2fe..1aa7bb2f 100644
--- a/src/settings/surreal/index.ts
+++ b/frontend/src/settings/surreal/index.ts
@@ -1,3 +1,2 @@
-export * from './createRequest';
export * from './surreal';
export { SurrealError } from './utils/surreal.error';
diff --git a/frontend/src/settings/surreal/surreal.ts b/frontend/src/settings/surreal/surreal.ts
new file mode 100644
index 00000000..ea2462df
--- /dev/null
+++ b/frontend/src/settings/surreal/surreal.ts
@@ -0,0 +1,134 @@
+import type { ResponseError, SurrealDbError } from 'surrealdb';
+import { ENV } from '@shared/env';
+import { attach, createEffect, createStore, sample } from 'effector';
+import { Surreal } from 'surrealdb';
+
+import { getDbTokenCookie, removeDbTokenCookie } from './utils/cookie.utils';
+import { SurrealError } from './utils/surreal.error';
+
+type TDbConfig = {
+ database: string;
+ endpoint: string;
+ namespace: string;
+};
+
+const DEFAULT_CONFIG: TDbConfig = {
+ database: ENV.SURREALDB_DB || 'test',
+ endpoint: ENV.SURREALDB_ENDPOINT || 'ws://127.0.0.1:8000/rpc',
+ namespace: ENV.SURREALDB_NS || 'test'
+};
+
+export const $db = createStore(null);
+export const $dbConfig = createStore(DEFAULT_CONFIG);
+
+export const getDbFx = attach({
+ effect: createEffect((db) => {
+ if (!isSurreal(db))
+ throw SurrealError.DatabaseOffline();
+ isConnected(db);
+
+ return db;
+ }),
+ source: $db
+});
+
+export const getAuthDbFx = createEffect(async () => {
+ const db = await getDbFx();
+ await isAuthenticated(db);
+
+ return db;
+});
+
+// connecting to database
+export const dbConnectFx = attach({
+ effect: createEffect(async (config) => {
+ const db = new Surreal();
+
+ await db.connect(config.endpoint);
+ await db.use({ database: config.database, namespace: config.namespace });
+ await db.ready;
+
+ return db;
+ }),
+ source: $dbConfig
+});
+
+export const dbConnected = dbConnectFx.done;
+export const dbFailConnected = dbConnectFx.fail;
+
+// disconnecting from the database
+export const dbDisconnectFx = attach({
+ effect: createEffect(async (db) => {
+ if (db)
+ await db.close();
+ }),
+ source: $db
+});
+
+export type TSurrealAuthenticateError = ResponseError | SurrealError;
+// setting up an authenticated connection
+export const dbAuthenticateFx = createEffect(async () => {
+ const db = await getDbFx();
+
+ const token = getDbTokenCookie();
+ if (!token)
+ throw SurrealError.DatabaseTokenMissing();
+
+ await db.authenticate(token);
+
+ return db;
+});
+
+export const dbInvalidateFx = createEffect(async () => {
+ const db = await getDbFx();
+ await db.invalidate();
+
+ return true as const;
+});
+
+export const removeDbTokenCookieFx = createEffect(() => {
+ removeDbTokenCookie();
+});
+
+// verifying the authenticity of a database connection
+export const dbIsAuthenticatedFx = createEffect(async () => {
+ const db = await getDbFx();
+ await isAuthenticated(db);
+
+ return true as const;
+});
+
+sample({
+ clock: dbConnectFx.doneData,
+ target: $db
+});
+
+sample({
+ clock: dbAuthenticateFx.done,
+ target: removeDbTokenCookieFx
+});
+
+// #region utils
+function isSurreal(db: unknown): db is Surreal {
+ return db instanceof Surreal;
+}
+
+function isConnected(db: Surreal): true {
+ if (!db.connection)
+ throw SurrealError.DatabaseOffline();
+
+ return true;
+}
+
+async function isAuthenticated(db: Surreal): Promise {
+ const info = await db.info();
+ if (!info || !info.id)
+ throw SurrealError.DatabaseUnauthenticated();
+
+ return true;
+}
+// #endregion
+
+dbAuthenticateFx.done.watch(() => console.warn('DB authenticated'));
+
+dbAuthenticateFx.failData.watch(error => console.error(error));
diff --git a/src/settings/surreal/utils/cookie.utils.ts b/frontend/src/settings/surreal/utils/cookie.utils.ts
similarity index 100%
rename from src/settings/surreal/utils/cookie.utils.ts
rename to frontend/src/settings/surreal/utils/cookie.utils.ts
diff --git a/src/settings/surreal/utils/surreal.error.ts b/frontend/src/settings/surreal/utils/surreal.error.ts
similarity index 52%
rename from src/settings/surreal/utils/surreal.error.ts
rename to frontend/src/settings/surreal/utils/surreal.error.ts
index d72c9785..472c0446 100644
--- a/src/settings/surreal/utils/surreal.error.ts
+++ b/frontend/src/settings/surreal/utils/surreal.error.ts
@@ -1,7 +1,9 @@
+type TSurrealErrorCode = 'ERR_OFFLINE' | 'ERR_TOKEN_MISSING' | 'ERR_UNAUTHENTICATED';
+
export class SurrealError extends Error {
- code: string;
+ code: TSurrealErrorCode;
- constructor(code: string, message: string) {
+ constructor(code: TSurrealErrorCode, message: string) {
super(message);
this.code = code;
}
@@ -14,7 +16,7 @@ export class SurrealError extends Error {
return new SurrealError('ERR_TOKEN_MISSING', 'Database token is missing!');
}
- static DatabaseUnauthorized() {
- return new SurrealError('ERR_UNAUTHORIZED', 'Database unauthorized!');
+ static DatabaseUnauthenticated() {
+ return new SurrealError('ERR_UNAUTHENTICATED', 'Database unauthenticated!');
}
}
diff --git a/frontend/src/shared/api/index.ts b/frontend/src/shared/api/index.ts
new file mode 100644
index 00000000..4333d508
--- /dev/null
+++ b/frontend/src/shared/api/index.ts
@@ -0,0 +1,3 @@
+export * from './request.api';
+export * from './session.api';
+export * from './utils/getErrorStatus.utils';
diff --git a/src/shared/api/request.api.ts b/frontend/src/shared/api/request.api.ts
similarity index 76%
rename from src/shared/api/request.api.ts
rename to frontend/src/shared/api/request.api.ts
index 17079437..e356065b 100644
--- a/src/shared/api/request.api.ts
+++ b/frontend/src/shared/api/request.api.ts
@@ -1,11 +1,12 @@
+import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import { ENV } from '@shared/env';
-import axios, { type AxiosError, type AxiosRequestConfig, type AxiosResponse } from 'axios';
+import axios from 'axios';
import { createEffect } from 'effector';
const api = axios.create({
baseURL: `${ENV.API_BASE_URL}`,
timeout: 5000,
- validateStatus: (status) => status >= 200 && status < 300
+ validateStatus: status => status >= 200 && status < 300
});
type Request = {
@@ -23,15 +24,16 @@ export const requestFx = createEffect((request) => {
url: request.url,
...request.options
})
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
.then((response: AxiosResponse) => response.data)
.catch((error: AxiosError) => {
let status: string = '';
if (error.response) {
status = error.response.status.toString();
- } else if (error.request) {
+ }
+ else if (error.request) {
status = (error.request as { [key: string]: unknown; status: number }).status.toString();
- } else if (error.status) {
+ }
+ else if (error.status) {
status = error.status.toString();
}
diff --git a/src/shared/api/session.api.ts b/frontend/src/shared/api/session.api.ts
similarity index 75%
rename from src/shared/api/session.api.ts
rename to frontend/src/shared/api/session.api.ts
index 30095678..13efeee6 100644
--- a/src/shared/api/session.api.ts
+++ b/frontend/src/shared/api/session.api.ts
@@ -1,4 +1,4 @@
-import { type TSession } from '@settings/session';
+import type { TSession } from '@settings/session';
import { createEffect } from 'effector';
import { requestFx } from './request.api';
@@ -33,13 +33,3 @@ export const sessionSignOutFx = createEffect(() => {
url: '/auth/refresh_token/signout'
});
});
-
-export const requestDbTokenFx = createEffect(() => {
- return requestFx({
- method: 'GET',
- options: {
- withCredentials: true
- },
- url: '/auth/token_db'
- });
-});
diff --git a/frontend/src/shared/api/utils/getErrorStatus.utils.ts b/frontend/src/shared/api/utils/getErrorStatus.utils.ts
new file mode 100644
index 00000000..cf4f6bd3
--- /dev/null
+++ b/frontend/src/shared/api/utils/getErrorStatus.utils.ts
@@ -0,0 +1,17 @@
+import type { AxiosError } from 'axios';
+
+export function getErrorStatus(error: AxiosError) {
+ let status: string = '';
+
+ if (error.response) {
+ status = error.response.status.toString();
+ }
+ else if (error.request) {
+ status = (error.request as { [key: string]: unknown; status: number }).status.toString();
+ }
+ else if (error.status) {
+ status = error.status.toString();
+ }
+
+ return status;
+}
diff --git a/src/shared/env/app.env.ts b/frontend/src/shared/env/app.env.ts
similarity index 100%
rename from src/shared/env/app.env.ts
rename to frontend/src/shared/env/app.env.ts
diff --git a/src/shared/env/index.ts b/frontend/src/shared/env/index.ts
similarity index 100%
rename from src/shared/env/index.ts
rename to frontend/src/shared/env/index.ts
diff --git a/src/shared/exceptions/business.error.ts b/frontend/src/shared/exceptions/business.error.ts
similarity index 100%
rename from src/shared/exceptions/business.error.ts
rename to frontend/src/shared/exceptions/business.error.ts
diff --git a/src/shared/exceptions/index.ts b/frontend/src/shared/exceptions/index.ts
similarity index 100%
rename from src/shared/exceptions/index.ts
rename to frontend/src/shared/exceptions/index.ts
diff --git a/src/shared/factories/createFormInput.ts b/frontend/src/shared/factories/createFormInput.ts
similarity index 73%
rename from src/shared/factories/createFormInput.ts
rename to frontend/src/shared/factories/createFormInput.ts
index e782b61a..5a28d39a 100644
--- a/src/shared/factories/createFormInput.ts
+++ b/frontend/src/shared/factories/createFormInput.ts
@@ -1,15 +1,16 @@
+import type { EventCallable, StoreWritable } from 'effector';
+import type { ZodSchema } from 'zod';
import {
createEffect,
createEvent,
createStore,
- type EventCallable,
+
is,
sample,
- split,
- type StoreWritable
+ split
+
} from 'effector';
import { and } from 'patronum';
-import { type ZodSchema } from 'zod';
type TInputError = null | string;
@@ -19,7 +20,7 @@ type TManipulationTools = {
inputBlur: EventCallable;
inputChanged: EventCallable>;
inputFocus: EventCallable;
- inputRefChanged: EventCallable;
+ inputRefChanged: EventCallable;
};
type TManipulationToolsExtension = {
@@ -48,18 +49,18 @@ export type TNamedManipulationTools<
> = TComputedNamedManipulationTools;
/**
- * @typedef {Object} TFieldManipulationTools
- * @property {StoreWritable} ${name}Input
- * @property {StoreWritable} ${name}InputRef
- * @property {EventCallable} ${name}InputRefChanged
- * @property {EventCallable} ${name}InputBlur
- * @property {EventCallable} ${name}InputFocused
- * @property {EventCallable} ${name}Changed
+ * @typedef {object} TFieldManipulationTools
+ * @property {StoreWritable} ${name}Input - стор для значения поля
+ * @property {StoreWritable} ${name}InputRef - стор для ссылки на элемент поля
+ * @property {EventCallable} ${name}InputRefChanged - событие для изменения ссылки на элемент поля
+ * @property {EventCallable} ${name}InputBlur - событие для потери фокуса полем
+ * @property {EventCallable} ${name}InputFocused - событие для получения фокуса полем
+ * @property {EventCallable} ${name}Changed - событие для изменения значения поля
*
- * @property {StoreWritable} ${name}Error
- * @property {EventCallable} ${name}ErrorClear
- * @property {EventCallable} ${name}InputFocusedDueError
- * @property {EventCallable} ${name}InputValidated
+ * @property {StoreWritable} ${name}Error - стор для ошибки поля
+ * @property {EventCallable} ${name}ErrorClear - событие для очистки ошибки поля
+ * @property {EventCallable} ${name}InputFocusedDueError - событие для получения фокуса полем при наличии ошибки
+ * @property {EventCallable} ${name}InputValidated - событие для валидации поля
*/
/**
@@ -72,6 +73,7 @@ export type TNamedManipulationTools<
* @param {?ZodSchema} [schema] - Zod схема для валидации
* @returns {TFieldManipulationTools} объект с инструментами манипуляции полем
*/
+
export function createFormInput(
name: Prefix,
source: StoreWritable | T
@@ -94,7 +96,8 @@ export function createFormInput(
let $input: StoreWritable;
if (is.store(source)) {
$input = source;
- } else {
+ }
+ else {
$input = createStore(source);
}
@@ -111,7 +114,7 @@ export function createFormInput(
// #region $inputRef
const $inputRef = createStore(null);
- const inputRefChanged = createEvent();
+ const inputRefChanged = createEvent();
$inputRef.on(inputRefChanged, (_, input) => input);
// #endregion
@@ -131,7 +134,7 @@ export function createFormInput(
sample({
clock: inputBlur,
source: $inputError,
- filter: $inputError.map((error) => !!error && error === 'empty'),
+ filter: $inputError.map(error => !!error && error === 'empty'),
target: inputErrorClear
});
@@ -139,7 +142,7 @@ export function createFormInput(
clock: inputFocusedDueError,
source: $inputRef,
filter: and($inputError, $inputRef),
- fn: (input) => input as HTMLInputElement,
+ fn: input => input as HTMLInputElement,
target: inputFocusedFx
});
@@ -148,9 +151,10 @@ export function createFormInput(
source: $input,
fn: (input) => {
const result = schema.safeParse(input);
- if (result.success) return null;
+ if (result.success)
+ return null;
- return result.error.issues[0].message as TInputError;
+ return result.error.issues[0]?.message as TInputError;
},
target: inputCompletedValidate
});
@@ -160,7 +164,7 @@ export function createFormInput(
clearError: inputErrorClear,
setError: [$inputError, inputFocusedDueError]
},
- match: (error) => (error ? 'setError' : 'clearError'),
+ match: error => (error ? 'setError' : 'clearError'),
source: inputCompletedValidate
});
@@ -183,6 +187,6 @@ export function createFormInput(
[`$${name}InputRef`]: $inputRef,
...extensionsTools
} as
- | TNamedManipulationTools>
- | TNamedManipulationTools