ソースを参照

chore: cleanup trpc related code

Nicolas Meienberger 1 年間 前
コミット
36675a67d4

+ 2 - 2
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 { useTranslations } from 'next-intl';
 import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from '@/components/ui/DropdownMenu';
 import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from '@/components/ui/DropdownMenu';
-import { AppWithInfo } from '@/client/core/types';
 import { Button } from '@/components/ui/Button';
 import { Button } from '@/components/ui/Button';
+import type { AppService } from '@/server/services/apps/apps.service';
 
 
 interface IProps {
 interface IProps {
-  app: AppWithInfo;
+  app: Awaited<ReturnType<AppService['getApp']>>;
   status?: AppStatus;
   status?: AppStatus;
   updateAvailable: boolean;
   updateAvailable: boolean;
   localDomain?: string;
   localDomain?: string;

+ 2 - 2
src/app/(dashboard)/app-store/[id]/components/AppDetailsContainer/AppDetailsContainer.tsx

@@ -3,7 +3,6 @@
 import React from 'react';
 import React from 'react';
 import { toast } from 'react-hot-toast';
 import { toast } from 'react-hot-toast';
 import { useTranslations } from 'next-intl';
 import { useTranslations } from 'next-intl';
-import { AppRouterOutput } from '@/server/routers/app/app.router';
 import { useDisclosure } from '@/client/hooks/useDisclosure';
 import { useDisclosure } from '@/client/hooks/useDisclosure';
 import { useAction } from 'next-safe-action/hook';
 import { useAction } from 'next-safe-action/hook';
 import { installAppAction } from '@/actions/app-actions/install-app-action';
 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 } from '@/components/AppStatus';
 import { AppStatus as AppStatusEnum } from '@/server/db/schema';
 import { AppStatus as AppStatusEnum } from '@/server/db/schema';
 import { castAppConfig } from '@/lib/helpers/castAppConfig';
 import { castAppConfig } from '@/lib/helpers/castAppConfig';
+import { AppService } from '@/server/services/apps/apps.service';
 import { InstallModal } from '../InstallModal';
 import { InstallModal } from '../InstallModal';
 import { StopModal } from '../StopModal';
 import { StopModal } from '../StopModal';
 import { UninstallModal } from '../UninstallModal';
 import { UninstallModal } from '../UninstallModal';
@@ -26,7 +26,7 @@ import { AppDetailsTabs } from '../AppDetailsTabs';
 import { FormValues } from '../InstallForm';
 import { FormValues } from '../InstallForm';
 
 
 interface IProps {
 interface IProps {
-  app: AppRouterOutput['getApp'];
+  app: Awaited<ReturnType<AppService['getApp']>>;
   localDomain?: string;
   localDomain?: string;
 }
 }
 type OpenType = 'local' | 'domain' | 'local_domain';
 type OpenType = 'local' | 'domain' | 'local_domain';

+ 1 - 2
src/app/(dashboard)/apps/page.tsx

@@ -1,7 +1,6 @@
 import { AppServiceClass } from '@/server/services/apps/apps.service';
 import { AppServiceClass } from '@/server/services/apps/apps.service';
 import { db } from '@/server/db';
 import { db } from '@/server/db';
 import React from 'react';
 import React from 'react';
-import { AppRouterOutput } from '@/server/routers/app/app.router';
 import { Metadata } from 'next';
 import { Metadata } from 'next';
 import { getTranslatorFromCookie } from '@/lib/get-translator';
 import { getTranslatorFromCookie } from '@/lib/get-translator';
 import { AppTile } from './components/AppTile';
 import { AppTile } from './components/AppTile';
@@ -19,7 +18,7 @@ export default async function Page() {
   const appsService = new AppServiceClass(db);
   const appsService = new AppServiceClass(db);
   const installedApps = await appsService.installedApps();
   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);
     const updateAvailable = Number(app.version) < Number(app.latestVersion);
 
 
     if (app.info?.available) return <AppTile key={app.id} app={app.info} status={app.status} updateAvailable={updateAvailable} />;
     if (app.info?.available) return <AppTile key={app.id} app={app.info} status={app.status} updateAvailable={updateAvailable} />;

+ 0 - 4
src/client/core/types.ts

@@ -1,4 +0,0 @@
-import * as Router from '../../server/routers/_app';
-
-export type App = Omit<Router.RouterOutput['app']['getApp'], 'info'>;
-export type AppWithInfo = Router.RouterOutput['app']['getApp'];

+ 0 - 109
src/client/hooks/__tests__/useLocale.test.ts

@@ -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);
-      });
-    });
-  });
-});

+ 0 - 30
src/client/hooks/useLocale.ts

@@ -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 };
-};

+ 0 - 64
src/client/mocks/fixtures/app.fixtures.ts

@@ -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>): 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<Partial<App>, 'info'>;
-  overridesInfo?: Partial<AppInfo>;
-  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,
-  };
-};

+ 0 - 91
src/client/mocks/getTrpcMock.ts

@@ -1,91 +0,0 @@
-import { rest } from 'msw';
-import SuperJSON from 'superjson';
-import type { RouterInput, RouterOutput } from '../../server/routers/_app';
-
-type RpcSuccessResponse<Data> = {
-  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<string, string>;
-        tError: {
-          message: string;
-        };
-      };
-    };
-  };
-};
-
-const jsonRpcSuccessResponse = (data: unknown): RpcSuccessResponse<unknown> => {
-  const response = SuperJSON.serialize(data);
-
-  return {
-    id: null,
-    result: { type: 'data', data: response },
-  };
-};
-
-const jsonRpcErrorResponse = (path: string, status: number, message: string, zodError?: Record<string, string>): 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<string, string>;
-}) => {
-  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))),
-  );
-};

+ 1 - 56
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 = [];

+ 0 - 96
src/client/utils/__tests__/page-helpers.test.ts

@@ -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);
-  });
-});

+ 0 - 42
src/client/utils/page-helpers.ts

@@ -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,
-    },
-  };
-};

+ 0 - 41
src/client/utils/trpc.ts

@@ -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<AppRouter>({
-  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,
-});

+ 9 - 36
src/pages/_app.tsx

@@ -1,17 +1,12 @@
 import React, { useEffect } from 'react';
 import React, { useEffect } from 'react';
-import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
 import type { AppProps } from 'next/app';
 import type { AppProps } from 'next/app';
 import Head from 'next/head';
 import Head from 'next/head';
-import { NextIntlProvider, createTranslator } from 'next-intl';
 import '../client/styles/global.css';
 import '../client/styles/global.css';
 import '../client/styles/global.scss';
 import '../client/styles/global.scss';
 import 'react-tooltip/dist/react-tooltip.css';
 import 'react-tooltip/dist/react-tooltip.css';
 import { Toaster } from 'react-hot-toast';
 import { Toaster } from 'react-hot-toast';
-import { useLocale } from '@/client/hooks/useLocale';
 import { useUIStore } from '../client/state/uiStore';
 import { useUIStore } from '../client/state/uiStore';
 import { StatusProvider } from '../client/components/hoc/StatusProvider';
 import { StatusProvider } from '../client/components/hoc/StatusProvider';
-import { trpc } from '../client/utils/trpc';
-import { SystemStatus, useSystemStore } from '../client/state/systemStore';
 
 
 /**
 /**
  * Next.js App component
  * Next.js App component
@@ -20,18 +15,7 @@ import { SystemStatus, useSystemStore } from '../client/state/systemStore';
  * @returns {JSX.Element} - JSX element
  * @returns {JSX.Element} - JSX element
  */
  */
 function MyApp({ Component, pageProps }: AppProps) {
 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
   // check theme on component mount
   useEffect(() => {
   useEffect(() => {
@@ -47,28 +31,17 @@ function MyApp({ Component, pageProps }: AppProps) {
     themeCheck();
     themeCheck();
   }, [setDarkMode]);
   }, [setDarkMode]);
 
 
-  useEffect(() => {
-    const translator = createTranslator({
-      messages: pageProps.messages,
-      locale,
-    });
-    setTranslator(translator);
-  }, [pageProps.messages, locale, setTranslator]);
-
   return (
   return (
     <main className="h-100">
     <main className="h-100">
-      <NextIntlProvider locale={locale} messages={pageProps.messages}>
-        <Head>
-          <title>Tipi</title>
-        </Head>
-        <StatusProvider>
-          <Component {...pageProps} />
-        </StatusProvider>
-        <Toaster />
-      </NextIntlProvider>
-      <ReactQueryDevtools />
+      <Head>
+        <title>Tipi</title>
+      </Head>
+      <StatusProvider>
+        <Component {...pageProps} />
+      </StatusProvider>
+      <Toaster />
     </main>
     </main>
   );
   );
 }
 }
 
 
-export default trpc.withTRPC(MyApp);
+export default MyApp;

+ 0 - 9
src/pages/api/trpc/[trpc].ts

@@ -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,
-});

+ 0 - 46
src/server/context.ts

@@ -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<typeof createContext>;

+ 0 - 17
src/server/routers/_app.ts

@@ -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<AppRouter>;
-export type RouterOutput = inferRouterOutputs<AppRouter>;

+ 0 - 27
src/server/routers/app/app.router.ts

@@ -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<typeof appRouter>;
-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()),
-});

+ 0 - 11
src/server/routers/auth/auth.router.ts

@@ -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 })),
-});

+ 0 - 7
src/server/routers/routers.test.ts

@@ -1,7 +0,0 @@
-import { mainRouter } from './_app';
-
-describe('routers', () => {
-  it('should return a router', () => {
-    expect(mainRouter).toBeDefined();
-  });
-});

+ 0 - 16
src/server/routers/system/system.router.ts

@@ -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<typeof systemRouter>;
-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),
-});

+ 2 - 0
src/server/services/apps/apps.service.ts

@@ -370,3 +370,5 @@ export class AppServiceClass {
       .filter(notEmpty);
       .filter(notEmpty);
   };
   };
 }
 }
+
+export type AppService = InstanceType<typeof AppServiceClass>;

+ 0 - 72
src/server/trpc.ts

@@ -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<string>} errors - errors
- */
-function zodErrorsToRecord(errors: typeToFlattenedError<string>) {
-  const record: Record<string, string> = {};
-  Object.entries(errors.fieldErrors).forEach(([key, value]) => {
-    const error = value?.[0];
-    if (error) {
-      record[key] = error;
-    }
-  });
-
-  return record;
-}
-
-const t = initTRPC.context<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);

+ 0 - 58
tests/TRPCTestClientProvider.tsx

@@ -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<AppRouter>({
-  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 (
-    <trpc.Provider client={trpcClient} queryClient={queryClient}>
-      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
-    </trpc.Provider>
-  );
-}

+ 3 - 6
tests/test-utils.tsx

@@ -3,22 +3,19 @@ import { render, RenderOptions, renderHook } from '@testing-library/react';
 import { Toaster } from 'react-hot-toast';
 import { Toaster } from 'react-hot-toast';
 import { NextIntlProvider } from 'next-intl';
 import { NextIntlProvider } from 'next-intl';
 import ue from '@testing-library/user-event';
 import ue from '@testing-library/user-event';
-import { TRPCTestClientProvider } from './TRPCTestClientProvider';
 import messages from '../src/client/messages/en.json';
 import messages from '../src/client/messages/en.json';
 
 
 const userEvent = ue.setup();
 const userEvent = ue.setup();
 
 
 const AllTheProviders: FC<{ children: React.ReactNode }> = ({ children }) => (
 const AllTheProviders: FC<{ children: React.ReactNode }> = ({ children }) => (
   <NextIntlProvider locale="en" messages={messages}>
   <NextIntlProvider locale="en" messages={messages}>
-    <TRPCTestClientProvider>
-      {children}
-      <Toaster />
-    </TRPCTestClientProvider>
+    {children}
+    <Toaster />
   </NextIntlProvider>
   </NextIntlProvider>
 );
 );
 
 
 const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => render(ui, { wrapper: AllTheProviders, ...options });
 const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>) => render(ui, { wrapper: AllTheProviders, ...options });
-const customRenderHook = (callback: () => any, options?: Omit<RenderOptions, 'wrapper'>) => renderHook(callback, { wrapper: AllTheProviders, ...options });
+const customRenderHook = <Props, Result>(callback: (props: Props) => Result, options?: Omit<RenderOptions, 'wrapper'>) => renderHook(callback, { wrapper: AllTheProviders, ...options });
 
 
 export * from '@testing-library/react';
 export * from '@testing-library/react';
 export { customRender as render };
 export { customRender as render };