From 36675a67d410ca99a522fe759b940710b9aaa763 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Fri, 6 Oct 2023 08:55:31 +0200 Subject: [PATCH] chore: cleanup trpc related code --- .../[id]/components/AppActions/AppActions.tsx | 4 +- .../AppDetailsContainer.tsx | 4 +- src/app/(dashboard)/apps/page.tsx | 3 +- src/client/core/types.ts | 4 - src/client/hooks/__tests__/useLocale.test.ts | 109 ------------------ src/client/hooks/useLocale.ts | 30 ----- src/client/mocks/fixtures/app.fixtures.ts | 64 ---------- src/client/mocks/getTrpcMock.ts | 91 --------------- src/client/mocks/handlers.ts | 57 +-------- .../utils/__tests__/page-helpers.test.ts | 96 --------------- src/client/utils/page-helpers.ts | 42 ------- src/client/utils/trpc.ts | 41 ------- src/pages/_app.tsx | 45 ++------ src/pages/api/trpc/[trpc].ts | 9 -- src/server/context.ts | 46 -------- src/server/routers/_app.ts | 17 --- src/server/routers/app/app.router.ts | 27 ----- src/server/routers/auth/auth.router.ts | 11 -- src/server/routers/routers.test.ts | 7 -- src/server/routers/system/system.router.ts | 16 --- src/server/services/apps/apps.service.ts | 2 + src/server/trpc.ts | 72 ------------ tests/TRPCTestClientProvider.tsx | 58 ---------- tests/test-utils.tsx | 9 +- 24 files changed, 20 insertions(+), 844 deletions(-) delete mode 100644 src/client/core/types.ts delete mode 100644 src/client/hooks/__tests__/useLocale.test.ts delete mode 100644 src/client/hooks/useLocale.ts delete mode 100644 src/client/mocks/fixtures/app.fixtures.ts delete mode 100644 src/client/mocks/getTrpcMock.ts delete mode 100644 src/client/utils/__tests__/page-helpers.test.ts delete mode 100644 src/client/utils/page-helpers.ts delete mode 100644 src/client/utils/trpc.ts delete mode 100644 src/pages/api/trpc/[trpc].ts delete mode 100644 src/server/context.ts delete mode 100644 src/server/routers/_app.ts delete mode 100644 src/server/routers/app/app.router.ts delete mode 100644 src/server/routers/auth/auth.router.ts delete mode 100644 src/server/routers/routers.test.ts delete mode 100644 src/server/routers/system/system.router.ts delete mode 100644 src/server/trpc.ts delete mode 100644 tests/TRPCTestClientProvider.tsx diff --git a/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.tsx b/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.tsx index ea428fcc..7f317c20 100644 --- a/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.tsx +++ b/src/app/(dashboard)/app-store/[id]/components/AppActions/AppActions.tsx @@ -5,11 +5,11 @@ import type { AppStatus } from '@/server/db/schema'; import { useTranslations } from 'next-intl'; import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from '@/components/ui/DropdownMenu'; -import { AppWithInfo } from '@/client/core/types'; import { Button } from '@/components/ui/Button'; +import type { AppService } from '@/server/services/apps/apps.service'; interface IProps { - app: AppWithInfo; + app: Awaited>; status?: AppStatus; updateAvailable: boolean; localDomain?: string; diff --git a/src/app/(dashboard)/app-store/[id]/components/AppDetailsContainer/AppDetailsContainer.tsx b/src/app/(dashboard)/app-store/[id]/components/AppDetailsContainer/AppDetailsContainer.tsx index cdd51864..2c8ffe43 100644 --- a/src/app/(dashboard)/app-store/[id]/components/AppDetailsContainer/AppDetailsContainer.tsx +++ b/src/app/(dashboard)/app-store/[id]/components/AppDetailsContainer/AppDetailsContainer.tsx @@ -3,7 +3,6 @@ import React from 'react'; import { toast } from 'react-hot-toast'; import { useTranslations } from 'next-intl'; -import { AppRouterOutput } from '@/server/routers/app/app.router'; import { useDisclosure } from '@/client/hooks/useDisclosure'; import { useAction } from 'next-safe-action/hook'; import { installAppAction } from '@/actions/app-actions/install-app-action'; @@ -16,6 +15,7 @@ import { AppLogo } from '@/components/AppLogo'; import { AppStatus } from '@/components/AppStatus'; import { AppStatus as AppStatusEnum } from '@/server/db/schema'; import { castAppConfig } from '@/lib/helpers/castAppConfig'; +import { AppService } from '@/server/services/apps/apps.service'; import { InstallModal } from '../InstallModal'; import { StopModal } from '../StopModal'; import { UninstallModal } from '../UninstallModal'; @@ -26,7 +26,7 @@ import { AppDetailsTabs } from '../AppDetailsTabs'; import { FormValues } from '../InstallForm'; interface IProps { - app: AppRouterOutput['getApp']; + app: Awaited>; localDomain?: string; } type OpenType = 'local' | 'domain' | 'local_domain'; diff --git a/src/app/(dashboard)/apps/page.tsx b/src/app/(dashboard)/apps/page.tsx index 65ef6e54..a7d136a0 100644 --- a/src/app/(dashboard)/apps/page.tsx +++ b/src/app/(dashboard)/apps/page.tsx @@ -1,7 +1,6 @@ import { AppServiceClass } from '@/server/services/apps/apps.service'; import { db } from '@/server/db'; import React from 'react'; -import { AppRouterOutput } from '@/server/routers/app/app.router'; import { Metadata } from 'next'; import { getTranslatorFromCookie } from '@/lib/get-translator'; import { AppTile } from './components/AppTile'; @@ -19,7 +18,7 @@ export default async function Page() { const appsService = new AppServiceClass(db); const installedApps = await appsService.installedApps(); - const renderApp = (app: AppRouterOutput['installedApps'][number]) => { + const renderApp = (app: (typeof installedApps)[number]) => { const updateAvailable = Number(app.version) < Number(app.latestVersion); if (app.info?.available) return ; diff --git a/src/client/core/types.ts b/src/client/core/types.ts deleted file mode 100644 index b0fc52ab..00000000 --- a/src/client/core/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -import * as Router from '../../server/routers/_app'; - -export type App = Omit; -export type AppWithInfo = Router.RouterOutput['app']['getApp']; diff --git a/src/client/hooks/__tests__/useLocale.test.ts b/src/client/hooks/__tests__/useLocale.test.ts deleted file mode 100644 index c6969bbc..00000000 --- a/src/client/hooks/__tests__/useLocale.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { getTRPCMock } from '@/client/mocks/getTrpcMock'; -import { server } from '@/client/mocks/server'; -import { deleteCookie, setCookie, getCookie } from 'cookies-next'; -import { renderHook, waitFor } from '../../../../tests/test-utils'; -import { useLocale } from '../useLocale'; - -beforeEach(() => { - deleteCookie('tipi-locale'); -}); - -describe('test: useLocale()', () => { - describe('test: locale', () => { - it('should return users locale if logged in', async () => { - // arrange - const locale = 'fr-FR'; - // @ts-expect-error - we're mocking the trpc context - server.use(getTRPCMock({ path: ['auth', 'me'], response: { locale } })); - - // act - const { result } = renderHook(() => useLocale()); - - // assert - await waitFor(() => { - expect(result.current.locale).toEqual(locale); - }); - }); - - it('should return cookie locale if not logged in', async () => { - // arrange - const locale = 'fr-FR'; - setCookie('tipi-locale', locale); - server.use(getTRPCMock({ path: ['auth', 'me'], response: null })); - - // act - const { result } = renderHook(() => useLocale()); - - // assert - await waitFor(() => { - expect(result.current.locale).toEqual(locale); - }); - }); - - it('should return browser locale if not logged in and no cookie', async () => { - // arrange - const locale = 'fr-FR'; - jest.spyOn(window.navigator, 'language', 'get').mockReturnValueOnce(locale); - server.use(getTRPCMock({ path: ['auth', 'me'], response: null })); - - // act - const { result } = renderHook(() => useLocale()); - - // assert - await waitFor(() => { - expect(result.current.locale).toEqual(locale); - }); - }); - - it('should default to english if no locale is found', async () => { - // arrange - server.use(getTRPCMock({ path: ['auth', 'me'], response: null })); - // @ts-expect-error - we're mocking window.navigator - jest.spyOn(window.navigator, 'language', 'get').mockReturnValueOnce(undefined); - - // act - const { result } = renderHook(() => useLocale()); - - // assert - await waitFor(() => { - expect(result.current.locale).toEqual('en-US'); - }); - }); - }); - - describe('test: changeLocale()', () => { - it('should set the locale in the cookie', async () => { - // arrange - const locale = 'fr-FR'; - const { result } = renderHook(() => useLocale()); - - // act - result.current.changeLocale(locale); - - // assert - await waitFor(() => { - expect(getCookie('tipi-locale')).toEqual('fr-FR'); - }); - }); - - it('should update the locale in the user profile when logged in', async () => { - // arrange - const locale = 'en'; - // @ts-expect-error - we're mocking the trpc context - server.use(getTRPCMock({ path: ['auth', 'me'], response: { locale: 'fr-FR' } })); - server.use(getTRPCMock({ path: ['auth', 'changeLocale'], type: 'mutation', response: true })); - const { result } = renderHook(() => useLocale()); - await waitFor(() => { - expect(result.current.locale).toEqual('fr-FR'); - }); - - // act - result.current.changeLocale(locale); - - // assert - await waitFor(() => { - expect(getCookie('tipi-locale')).toEqual(locale); - }); - }); - }); -}); diff --git a/src/client/hooks/useLocale.ts b/src/client/hooks/useLocale.ts deleted file mode 100644 index a254daa0..00000000 --- a/src/client/hooks/useLocale.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { setCookie, getCookie } from 'cookies-next'; -import { useRouter } from 'next/router'; -import { trpc } from '@/utils/trpc'; -import { Locale, getLocaleFromString } from '@/shared/internationalization/locales'; - -export const useLocale = () => { - const router = useRouter(); - const me = trpc.auth.me.useQuery(); - const changeUserLocale = trpc.auth.changeLocale.useMutation(); - const browserLocale = typeof window !== 'undefined' ? window.navigator.language : undefined; - const cookieLocale = getCookie('tipi-locale'); - - const locale = String(me.data?.locale || cookieLocale || browserLocale || 'en'); - const ctx = trpc.useContext(); - - const changeLocale = async (l: Locale) => { - if (me.data) { - await changeUserLocale.mutateAsync({ locale: l }); - await ctx.invalidate(); - } - - setCookie('tipi-locale', l, { - expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), - }); - - router.reload(); - }; - - return { locale: getLocaleFromString(locale), changeLocale }; -}; diff --git a/src/client/mocks/fixtures/app.fixtures.ts b/src/client/mocks/fixtures/app.fixtures.ts deleted file mode 100644 index a0afc86a..00000000 --- a/src/client/mocks/fixtures/app.fixtures.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { faker } from '@faker-js/faker'; -import type { AppStatus } from '@/server/db/schema'; -import { AppInfo, AppCategory, APP_CATEGORIES } from '@runtipi/shared'; -import { App, AppWithInfo } from '../../core/types'; - -const randomCategory = (): AppCategory[] => { - const categories = Object.values(APP_CATEGORIES); - const randomIndex = faker.number.int({ min: 0, max: categories.length - 1 }); - return [categories[randomIndex] as AppCategory]; -}; - -const createApp = (overrides?: Partial): AppInfo => { - const name = faker.lorem.word(); - return { - id: name.toLowerCase(), - name, - description: faker.lorem.words(), - author: faker.lorem.word(), - available: true, - categories: randomCategory(), - form_fields: [], - port: faker.number.int({ min: 1000, max: 9999 }), - short_desc: faker.lorem.words(), - tipi_version: 1, - version: faker.system.semver(), - source: faker.internet.url(), - https: false, - no_gui: false, - exposable: true, - url_suffix: '', - force_expose: false, - generate_vapid_keys: false, - ...overrides, - }; -}; - -type CreateAppEntityParams = { - overrides?: Omit, 'info'>; - overridesInfo?: Partial; - status?: AppStatus; -}; - -export const createAppEntity = (params: CreateAppEntityParams): AppWithInfo => { - const { overrides, overridesInfo, status = 'running' } = params; - - const id = faker.lorem.word().toLowerCase(); - const app = createApp({ id, ...overridesInfo }); - return { - id, - status, - info: app, - config: {}, - exposed: false, - domain: null, - version: 1, - lastOpened: faker.date.past().toISOString(), - numOpened: 0, - createdAt: faker.date.past().toISOString(), - updatedAt: faker.date.past().toISOString(), - latestVersion: 1, - latestDockerVersion: '1.0.0', - ...overrides, - }; -}; diff --git a/src/client/mocks/getTrpcMock.ts b/src/client/mocks/getTrpcMock.ts deleted file mode 100644 index 8a7123c7..00000000 --- a/src/client/mocks/getTrpcMock.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { rest } from 'msw'; -import SuperJSON from 'superjson'; -import type { RouterInput, RouterOutput } from '../../server/routers/_app'; - -type RpcSuccessResponse = { - id: null; - result: { type: 'data'; data: Data }; -}; - -type RpcErrorResponse = { - error: { - json: { - message: string; - code: number; - data: { - code: string; - httpStatus: number; - stack: string; - path: string; // TQuery - zodError?: Record; - tError: { - message: string; - }; - }; - }; - }; -}; - -const jsonRpcSuccessResponse = (data: unknown): RpcSuccessResponse => { - const response = SuperJSON.serialize(data); - - return { - id: null, - result: { type: 'data', data: response }, - }; -}; - -const jsonRpcErrorResponse = (path: string, status: number, message: string, zodError?: Record): RpcErrorResponse => ({ - error: { - json: { - message, - code: -32600, - data: { - code: 'INTERNAL_SERVER_ERROR', - httpStatus: status, - stack: 'Error: Internal Server Error', - path, - zodError, - tError: { - message, - }, - }, - }, - }, -}); - -export const getTRPCMock = < - K1 extends keyof RouterInput, - K2 extends keyof RouterInput[K1], // object itself - O extends RouterOutput[K1][K2], // all its keys ->(endpoint: { - path: [K1, K2]; - response: O; - type?: 'query' | 'mutation'; - delay?: number; -}) => { - const fn = endpoint.type === 'mutation' ? rest.post : rest.get; - - const route = `http://localhost:3000/api/trpc/${endpoint.path[0]}.${endpoint.path[1] as string}`; - - return fn(route, (_, res, ctx) => res(ctx.delay(endpoint.delay), ctx.json(jsonRpcSuccessResponse(endpoint.response)))); -}; - -export const getTRPCMockError = < - K1 extends keyof RouterInput, - K2 extends keyof RouterInput[K1], // object itself ->(endpoint: { - path: [K1, K2]; - type?: 'query' | 'mutation'; - status?: number; - message?: string; - zodError?: Record; -}) => { - const fn = endpoint.type === 'mutation' ? rest.post : rest.get; - - const route = `http://localhost:3000/api/trpc/${endpoint.path[0]}.${endpoint.path[1] as string}`; - - return fn(route, (_, res, ctx) => - res(ctx.delay(), ctx.json(jsonRpcErrorResponse(`${endpoint.path[0]}.${endpoint.path[1] as string}`, endpoint.status ?? 500, endpoint.message ?? 'Internal Server Error', endpoint.zodError))), - ); -}; diff --git a/src/client/mocks/handlers.ts b/src/client/mocks/handlers.ts index 80007cbc..0f3710c6 100644 --- a/src/client/mocks/handlers.ts +++ b/src/client/mocks/handlers.ts @@ -1,56 +1 @@ -import { faker } from '@faker-js/faker'; -import { createAppEntity } from './fixtures/app.fixtures'; -import { getTRPCMock } from './getTrpcMock'; -import { createAppConfig } from '../../server/tests/apps.factory'; - -export const handlers = [ - getTRPCMock({ - path: ['system', 'getVersion'], - type: 'query', - response: { current: '1.0.0', latest: '1.0.0', body: 'hello' }, - }), - getTRPCMock({ - path: ['system', 'restart'], - type: 'mutation', - response: true, - delay: 100, - }), - getTRPCMock({ - path: ['system', 'getSettings'], - type: 'query', - response: { internalIp: 'localhost', dnsIp: '1.1.1.1', appsRepoUrl: 'https://test.com/test', domain: 'tipi.localhost', localDomain: 'tipi.lan' }, - }), - getTRPCMock({ - path: ['system', 'updateSettings'], - type: 'mutation', - response: undefined, - }), - // Auth - getTRPCMock({ - path: ['auth', 'me'], - type: 'query', - response: { - totpEnabled: false, - id: faker.number.int(), - username: faker.internet.userName(), - locale: 'en', - operator: true, - }, - }), - // App - getTRPCMock({ - path: ['app', 'getApp'], - type: 'query', - response: createAppEntity({ status: 'running' }), - }), - getTRPCMock({ - path: ['app', 'installedApps'], - type: 'query', - response: [createAppEntity({ status: 'running' }), createAppEntity({ status: 'stopped' })], - }), - getTRPCMock({ - path: ['app', 'listApps'], - type: 'query', - response: { apps: [createAppConfig({}), createAppConfig({})], total: 2 }, - }), -]; +export const handlers = []; diff --git a/src/client/utils/__tests__/page-helpers.test.ts b/src/client/utils/__tests__/page-helpers.test.ts deleted file mode 100644 index 5d2908d8..00000000 --- a/src/client/utils/__tests__/page-helpers.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import merge from 'lodash.merge'; -import { deleteCookie, setCookie } from 'cookies-next'; -import { fromPartial } from '@total-typescript/shoehorn'; -import { TipiCache } from '@/server/core/TipiCache'; -import { getAuthedPageProps, getMessagesPageProps } from '../page-helpers'; -import englishMessages from '../../messages/en.json'; -import frenchMessages from '../../messages/fr-FR.json'; - -const cache = new TipiCache('page-helpers.test.ts'); - -afterAll(async () => { - await cache.close(); -}); - -describe('test: getAuthedPageProps()', () => { - it('should redirect to /login if there is no user id in session', async () => { - // arrange - const ctx = { req: { headers: {} } }; - - // act - // @ts-expect-error - we're passing in a partial context - const { redirect } = await getAuthedPageProps(ctx); - - // assert - expect(redirect.destination).toBe('/login'); - expect(redirect.permanent).toBe(false); - }); - - it('should return props if there is a user id in session', async () => { - // arrange - const ctx = { req: { headers: { 'x-session-id': '123' } } }; - await cache.set('session:123', '456'); - - // act - // @ts-expect-error - we're passing in a partial context - const { props } = await getAuthedPageProps(ctx); - - // assert - expect(props).toEqual({}); - }); -}); - -describe('test: getMessagesPageProps()', () => { - beforeEach(() => { - deleteCookie('tipi-locale'); - }); - - it('should return correct messages if the locale is in the session', async () => { - // arrange - const ctx = { req: { session: { locale: 'fr' }, headers: {} } }; - - // act - // @ts-expect-error - we're passing in a partial context - const { props } = await getMessagesPageProps(ctx); - - // assert - expect(props.messages).toEqual(merge(frenchMessages, englishMessages)); - }); - - it('should return correct messages if the locale in the cookie', async () => { - // arrange - const ctx = { req: { session: {}, headers: {} } }; - setCookie('tipi-locale', 'fr-FR', { req: fromPartial(ctx.req) }); - - // act - // @ts-expect-error - we're passing in a partial context - const { props } = await getMessagesPageProps(ctx); - - // assert - expect(props.messages).toEqual(merge(frenchMessages, englishMessages)); - }); - - it('should return correct messages if the locale is detected from the browser', async () => { - // arrange - const ctx = { req: { session: {}, headers: { 'accept-language': 'fr-FR' } } }; - - // act - // @ts-expect-error - we're passing in a partial context - const { props } = await getMessagesPageProps(ctx); - - // assert - expect(props.messages).toEqual(merge(frenchMessages, englishMessages)); - }); - - it('should default to english messages if the locale is not found', async () => { - // arrange - const ctx = { req: { session: {}, headers: {} } }; - - // act - // @ts-expect-error - we're passing in a partial context - const { props } = await getMessagesPageProps(ctx); - - // assert - expect(props.messages).toEqual(englishMessages); - }); -}); diff --git a/src/client/utils/page-helpers.ts b/src/client/utils/page-helpers.ts deleted file mode 100644 index 2fa561fe..00000000 --- a/src/client/utils/page-helpers.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { GetServerSideProps } from 'next'; -import merge from 'lodash.merge'; -import { getLocaleFromString } from '@/shared/internationalization/locales'; -import { getCookie } from 'cookies-next'; -import { TipiCache } from '@/server/core/TipiCache'; - -export const getAuthedPageProps: GetServerSideProps = async (ctx) => { - const cache = new TipiCache('getAuthedPageProps'); - const sessionId = ctx.req.headers['x-session-id']; - const userId = await cache.get(`session:${sessionId}`); - await cache.close(); - - if (!userId) { - return { - redirect: { - destination: '/login', - permanent: false, - }, - }; - } - - return { - props: {}, - }; -}; - -export const getMessagesPageProps: GetServerSideProps = async (ctx) => { - const cookieLocale = getCookie('tipi-locale', { req: ctx.req }); - const browserLocale = ctx.req.headers['accept-language']?.split(',')[0]; - - const locale = getLocaleFromString(String(cookieLocale || browserLocale || 'en')); - - const englishMessages = (await import(`../messages/en.json`)).default; - const messages = (await import(`../messages/${locale}.json`)).default; - const mergedMessages = merge(englishMessages, messages); - - return { - props: { - messages: mergedMessages, - }, - }; -}; diff --git a/src/client/utils/trpc.ts b/src/client/utils/trpc.ts deleted file mode 100644 index 3b142d88..00000000 --- a/src/client/utils/trpc.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { httpBatchLink, loggerLink } from '@trpc/client'; -import { createTRPCNext } from '@trpc/next'; -import superjson from 'superjson'; -import type { AppRouter } from '../../server/routers/_app'; - -/** - * Get base url for the current environment - * - * @returns {string} base url - */ -function getBaseUrl() { - if (typeof window !== 'undefined') { - // browser should use relative path - return ''; - } - - return `http://localhost:${process.env.PORT ?? 3000}`; -} - -export const trpc = createTRPCNext({ - config() { - return { - transformer: superjson, - links: [ - loggerLink({ - enabled: (opts) => process.env.NODE_ENV === 'development' || (opts.direction === 'down' && opts.result instanceof Error), - }), - httpBatchLink({ - url: `${getBaseUrl()}/api/trpc`, - fetch(url, options) { - return fetch(url, { - ...options, - credentials: 'include', - }); - }, - }), - ], - }; - }, - ssr: false, -}); diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index d269ed4e..42114858 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,17 +1,12 @@ import React, { useEffect } from 'react'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import type { AppProps } from 'next/app'; import Head from 'next/head'; -import { NextIntlProvider, createTranslator } from 'next-intl'; import '../client/styles/global.css'; import '../client/styles/global.scss'; import 'react-tooltip/dist/react-tooltip.css'; import { Toaster } from 'react-hot-toast'; -import { useLocale } from '@/client/hooks/useLocale'; import { useUIStore } from '../client/state/uiStore'; import { StatusProvider } from '../client/components/hoc/StatusProvider'; -import { trpc } from '../client/utils/trpc'; -import { SystemStatus, useSystemStore } from '../client/state/systemStore'; /** * Next.js App component @@ -20,18 +15,7 @@ import { SystemStatus, useSystemStore } from '../client/state/systemStore'; * @returns {JSX.Element} - JSX element */ function MyApp({ Component, pageProps }: AppProps) { - const { setDarkMode, setTranslator } = useUIStore(); - const { setStatus, setVersion, pollStatus } = useSystemStore(); - const { locale } = useLocale(); - - trpc.system.status.useQuery(undefined, { networkMode: 'online', refetchInterval: 2000, onSuccess: (d) => setStatus((d.status as SystemStatus) || 'RUNNING'), enabled: pollStatus }); - const version = trpc.system.getVersion.useQuery(undefined, { networkMode: 'online' }); - - useEffect(() => { - if (version.data) { - setVersion(version.data); - } - }, [setVersion, version.data]); + const { setDarkMode } = useUIStore(); // check theme on component mount useEffect(() => { @@ -47,28 +31,17 @@ function MyApp({ Component, pageProps }: AppProps) { themeCheck(); }, [setDarkMode]); - useEffect(() => { - const translator = createTranslator({ - messages: pageProps.messages, - locale, - }); - setTranslator(translator); - }, [pageProps.messages, locale, setTranslator]); - return (
- - - Tipi - - - - - - - + + Tipi + + + + +
); } -export default trpc.withTRPC(MyApp); +export default MyApp; diff --git a/src/pages/api/trpc/[trpc].ts b/src/pages/api/trpc/[trpc].ts deleted file mode 100644 index 396feeaf..00000000 --- a/src/pages/api/trpc/[trpc].ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as trpcNext from '@trpc/server/adapters/next'; -import { createContext } from '../../../server/context'; -import { mainRouter } from '../../../server/routers/_app'; - -// export API handler -export default trpcNext.createNextApiHandler({ - router: mainRouter, - createContext, -}); diff --git a/src/server/context.ts b/src/server/context.ts deleted file mode 100644 index 20233075..00000000 --- a/src/server/context.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { inferAsyncReturnType } from '@trpc/server'; -import { CreateNextContextOptions } from '@trpc/server/adapters/next'; -import { TipiCache } from './core/TipiCache/TipiCache'; - -type CreateContextOptions = { - req: CreateNextContextOptions['req']; - res: CreateNextContextOptions['res']; - sessionId: string; - userId?: number; -}; - -/** - * Use this helper for: - * - testing, so we dont have to mock Next.js' req/res - * - trpc's `createSSGHelpers` where we don't have req/res - * - * @param {CreateContextOptions} opts - options - * @see https://create.t3.gg/en/usage/trpc#-servertrpccontextts - */ -const createContextInner = async (opts: CreateContextOptions) => ({ - ...opts, -}); - -/** - * This is the actual context you'll use in your router - * - * @param {CreateNextContextOptions} opts - options - */ -export const createContext = async (opts: CreateNextContextOptions) => { - const { req, res } = opts; - - const sessionId = req.headers['x-session-id'] as string; - - const cache = new TipiCache('createContext'); - const userId = await cache.get(`session:${sessionId}`); - await cache.close(); - - return createContextInner({ - req, - res, - sessionId, - userId: Number(userId) || undefined, - }); -}; - -export type Context = inferAsyncReturnType; diff --git a/src/server/routers/_app.ts b/src/server/routers/_app.ts deleted file mode 100644 index 6da1d2a3..00000000 --- a/src/server/routers/_app.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { inferRouterInputs, inferRouterOutputs } from '@trpc/server'; -import { router } from '../trpc'; -import { appRouter } from './app/app.router'; -import { authRouter } from './auth/auth.router'; -import { systemRouter } from './system/system.router'; - -export const mainRouter = router({ - system: systemRouter, - auth: authRouter, - app: appRouter, -}); - -// export type definition of API -export type AppRouter = typeof mainRouter; - -export type RouterInput = inferRouterInputs; -export type RouterOutput = inferRouterOutputs; diff --git a/src/server/routers/app/app.router.ts b/src/server/routers/app/app.router.ts deleted file mode 100644 index 755c8f6a..00000000 --- a/src/server/routers/app/app.router.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { z } from 'zod'; -import { inferRouterOutputs } from '@trpc/server'; -import { db } from '@/server/db'; -import { AppServiceClass } from '../../services/apps/apps.service'; -import { router, protectedProcedure } from '../../trpc'; - -export type AppRouterOutput = inferRouterOutputs; -const AppService = new AppServiceClass(db); - -const formSchema = z.object({}).catchall(z.any()); - -export const appRouter = router({ - getApp: protectedProcedure.input(z.object({ id: z.string() })).query(({ input }) => AppService.getApp(input.id)), - startAllApp: protectedProcedure.mutation(AppService.startAllApps), - startApp: protectedProcedure.input(z.object({ id: z.string() })).mutation(({ input }) => AppService.startApp(input.id)), - installApp: protectedProcedure - .input(z.object({ id: z.string(), form: formSchema, exposed: z.boolean().optional(), domain: z.string().optional() })) - .mutation(({ input }) => AppService.installApp(input.id, input.form, input.exposed, input.domain)), - updateAppConfig: protectedProcedure - .input(z.object({ id: z.string(), form: formSchema, exposed: z.boolean().optional(), domain: z.string().optional() })) - .mutation(({ input }) => AppService.updateAppConfig(input.id, input.form, input.exposed, input.domain)), - stopApp: protectedProcedure.input(z.object({ id: z.string() })).mutation(({ input }) => AppService.stopApp(input.id)), - uninstallApp: protectedProcedure.input(z.object({ id: z.string() })).mutation(({ input }) => AppService.uninstallApp(input.id)), - updateApp: protectedProcedure.input(z.object({ id: z.string() })).mutation(({ input }) => AppService.updateApp(input.id)), - installedApps: protectedProcedure.query(AppService.installedApps), - listApps: protectedProcedure.query(() => AppServiceClass.listApps()), -}); diff --git a/src/server/routers/auth/auth.router.ts b/src/server/routers/auth/auth.router.ts deleted file mode 100644 index a9b7e28a..00000000 --- a/src/server/routers/auth/auth.router.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { z } from 'zod'; -import { AuthServiceClass } from '../../services/auth/auth.service'; -import { router, publicProcedure, protectedProcedure } from '../../trpc'; -import { db } from '../../db'; - -const AuthService = new AuthServiceClass(db); - -export const authRouter = router({ - me: publicProcedure.query(async ({ ctx }) => AuthService.me(ctx.userId)), - changeLocale: protectedProcedure.input(z.object({ locale: z.string() })).mutation(async ({ input, ctx }) => AuthService.changeLocale({ userId: Number(ctx.userId), locale: input.locale })), -}); diff --git a/src/server/routers/routers.test.ts b/src/server/routers/routers.test.ts deleted file mode 100644 index 3ea92096..00000000 --- a/src/server/routers/routers.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { mainRouter } from './_app'; - -describe('routers', () => { - it('should return a router', () => { - expect(mainRouter).toBeDefined(); - }); -}); diff --git a/src/server/routers/system/system.router.ts b/src/server/routers/system/system.router.ts deleted file mode 100644 index ee59aba7..00000000 --- a/src/server/routers/system/system.router.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { inferRouterOutputs } from '@trpc/server'; -import { settingsSchema } from '@runtipi/shared'; -import { router, protectedProcedure, publicProcedure } from '../../trpc'; -import { SystemServiceClass } from '../../services/system'; -import * as TipiConfig from '../../core/TipiConfig'; - -export type SystemRouterOutput = inferRouterOutputs; -const SystemService = new SystemServiceClass(); - -export const systemRouter = router({ - status: publicProcedure.query(SystemServiceClass.status), - getVersion: publicProcedure.query(SystemService.getVersion), - restart: protectedProcedure.mutation(SystemService.restart), - updateSettings: protectedProcedure.input(settingsSchema).mutation(({ input }) => TipiConfig.setSettings(input)), - getSettings: protectedProcedure.query(TipiConfig.getSettings), -}); diff --git a/src/server/services/apps/apps.service.ts b/src/server/services/apps/apps.service.ts index 4c9defd6..5d66de37 100644 --- a/src/server/services/apps/apps.service.ts +++ b/src/server/services/apps/apps.service.ts @@ -370,3 +370,5 @@ export class AppServiceClass { .filter(notEmpty); }; } + +export type AppService = InstanceType; diff --git a/src/server/trpc.ts b/src/server/trpc.ts deleted file mode 100644 index 88a071df..00000000 --- a/src/server/trpc.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { initTRPC, TRPCError } from '@trpc/server'; -import superjson from 'superjson'; -import { typeToFlattenedError, ZodError } from 'zod'; -import { type Context } from './context'; -import { AuthQueries } from './queries/auth/auth.queries'; -import { db } from './db'; -import { type MessageKey, TranslatedError } from './utils/errors'; - -const authQueries = new AuthQueries(db); - -/** - * Convert ZodError to a record - * - * @param {typeToFlattenedError} errors - errors - */ -function zodErrorsToRecord(errors: typeToFlattenedError) { - const record: Record = {}; - Object.entries(errors.fieldErrors).forEach(([key, value]) => { - const error = value?.[0]; - if (error) { - record[key] = error; - } - }); - - return record; -} - -const t = initTRPC.context().create({ - transformer: superjson, - errorFormatter({ shape, error }) { - return { - ...shape, - data: { - ...shape.data, - zodError: error.code === 'BAD_REQUEST' && error.cause instanceof ZodError ? zodErrorsToRecord(error.cause.flatten()) : null, - tError: - error.cause instanceof TranslatedError ? { message: error.cause.message as MessageKey, variables: error.cause.variableValues } : { message: error.message as MessageKey, variables: {} }, - }, - }; - }, -}); -// Base router and procedure helpers -export const { router } = t; - -/** - * Unprotected procedure - */ -export const publicProcedure = t.procedure; - -/** - * Reusable middleware to ensure - * users are logged in - */ -const isAuthed = t.middleware(async ({ ctx, next }) => { - const { userId } = ctx; - if (!userId) { - throw new TRPCError({ code: 'UNAUTHORIZED', message: 'You need to be logged in to perform this action' }); - } - - const user = await authQueries.getUserById(userId); - - if (!user) { - throw new TRPCError({ code: 'UNAUTHORIZED', message: 'You need to be logged in to perform this action' }); - } - - return next({ ctx }); -}); - -/** - * Protected procedure - */ -export const protectedProcedure = t.procedure.use(isAuthed); diff --git a/tests/TRPCTestClientProvider.tsx b/tests/TRPCTestClientProvider.tsx deleted file mode 100644 index 4ce8ec7b..00000000 --- a/tests/TRPCTestClientProvider.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { createTRPCReact, httpLink, loggerLink } from '@trpc/react-query'; -import React, { useState } from 'react'; -import fetch from 'isomorphic-fetch'; -import superjson from 'superjson'; - -import type { AppRouter } from '../src/server/routers/_app'; - -export const trpc = createTRPCReact({ - unstable_overrides: { - useMutation: { - async onSuccess(opts) { - await opts.originalFn(); - await opts.queryClient.invalidateQueries(); - }, - }, - }, -}); - -export function TRPCTestClientProvider(props: { children: React.ReactNode }) { - const { children } = props; - const [queryClient] = useState( - () => - new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, - }), - ); - - const [trpcClient] = useState(() => - trpc.createClient({ - transformer: superjson, - links: [ - loggerLink({ - enabled: () => false, - }), - httpLink({ - url: 'http://localhost:3000/api/trpc', - fetch: async (input, init?) => - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - isomorphic-fetch is missing the `signal` option - fetch(input, { - ...init, - }), - }), - ], - }), - ); - - return ( - - {children} - - ); -} diff --git a/tests/test-utils.tsx b/tests/test-utils.tsx index d16e78e8..c97a1917 100644 --- a/tests/test-utils.tsx +++ b/tests/test-utils.tsx @@ -3,22 +3,19 @@ import { render, RenderOptions, renderHook } from '@testing-library/react'; import { Toaster } from 'react-hot-toast'; import { NextIntlProvider } from 'next-intl'; import ue from '@testing-library/user-event'; -import { TRPCTestClientProvider } from './TRPCTestClientProvider'; import messages from '../src/client/messages/en.json'; const userEvent = ue.setup(); const AllTheProviders: FC<{ children: React.ReactNode }> = ({ children }) => ( - - {children} - - + {children} + ); const customRender = (ui: ReactElement, options?: Omit) => render(ui, { wrapper: AllTheProviders, ...options }); -const customRenderHook = (callback: () => any, options?: Omit) => renderHook(callback, { wrapper: AllTheProviders, ...options }); +const customRenderHook = (callback: (props: Props) => Result, options?: Omit) => renderHook(callback, { wrapper: AllTheProviders, ...options }); export * from '@testing-library/react'; export { customRender as render };