Explorar o código

Format code and fix biome error

arvid-e hai 1 mes
pai
achega
3297a2f7ee

+ 96 - 55
apps/app/src/client/components/Admin/MarkdownSetting/ContentDispositionSettings.tsx

@@ -1,9 +1,12 @@
-import React, { useState, useCallback, useEffect } from 'react';
-
+import type React from 'react';
+import { useCallback, useEffect, useState } from 'react';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { useForm } from 'react-hook-form';
 import { useForm } from 'react-hook-form';
 
 
-import { useContentDisposition, type ContentDispositionSettings } from '../../../services/admin-content-disposition';
+import {
+  type ContentDispositionSettings as ContentDispositionSettingsType,
+  useContentDisposition,
+} from '../../../services/admin-content-disposition';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
 
 interface MimeTypeListProps {
 interface MimeTypeListProps {
@@ -15,19 +18,32 @@ interface MimeTypeListProps {
   isUpdating: boolean;
   isUpdating: boolean;
 }
 }
 
 
-const normalizeMimeType = (mimeType: string): string => mimeType.trim().toLowerCase();
+const normalizeMimeType = (mimeType: string): string =>
+  mimeType.trim().toLowerCase();
 
 
 const MimeTypeList = ({
 const MimeTypeList = ({
-  title, items, emptyText, onRemove, removeLabel, isUpdating,
+  title,
+  items,
+  emptyText,
+  onRemove,
+  removeLabel,
+  isUpdating,
 }: MimeTypeListProps) => (
 }: MimeTypeListProps) => (
   <div className="col-md-6 col-sm-12 mb-4">
   <div className="col-md-6 col-sm-12 mb-4">
     <div className="card shadow-sm rounded-3">
     <div className="card shadow-sm rounded-3">
       <div className="card-header bg-transparent fw-bold">{title}</div>
       <div className="card-header bg-transparent fw-bold">{title}</div>
       <div className="card-body">
       <div className="card-body">
         <ul className="list-group list-group-flush">
         <ul className="list-group list-group-flush">
-          {items.length === 0 && <li className="list-group-item text-muted small border-0">{emptyText}</li>}
+          {items.length === 0 && (
+            <li className="list-group-item text-muted small border-0">
+              {emptyText}
+            </li>
+          )}
           {items.map((m: string) => (
           {items.map((m: string) => (
-            <li key={m} className="list-group-item d-flex justify-content-between align-items-center border-0 px-0">
+            <li
+              key={m}
+              className="list-group-item d-flex justify-content-between align-items-center border-0 px-0"
+            >
               <code>{m}</code>
               <code>{m}</code>
               <button
               <button
                 type="button"
                 type="button"
@@ -47,9 +63,8 @@ const MimeTypeList = ({
 
 
 const ContentDispositionSettings: React.FC = () => {
 const ContentDispositionSettings: React.FC = () => {
   const { t } = useTranslation('admin');
   const { t } = useTranslation('admin');
-  const {
-    currentSettings, isLoading, isUpdating, updateSettings,
-  } = useContentDisposition();
+  const { currentSettings, isLoading, isUpdating, updateSettings } =
+    useContentDisposition();
 
 
   const [currentInput, setCurrentInput] = useState<string>('');
   const [currentInput, setCurrentInput] = useState<string>('');
   const [error, setError] = useState<string | null>(null);
   const [error, setError] = useState<string | null>(null);
@@ -60,7 +75,7 @@ const ContentDispositionSettings: React.FC = () => {
     watch,
     watch,
     reset,
     reset,
     formState: { isDirty },
     formState: { isDirty },
-  } = useForm<ContentDispositionSettings>({
+  } = useForm<ContentDispositionSettingsType>({
     defaultValues: {
     defaultValues: {
       inlineMimeTypes: [],
       inlineMimeTypes: [],
       attachmentMimeTypes: [],
       attachmentMimeTypes: [],
@@ -76,45 +91,53 @@ const ContentDispositionSettings: React.FC = () => {
   const inlineMimeTypes = watch('inlineMimeTypes');
   const inlineMimeTypes = watch('inlineMimeTypes');
   const attachmentMimeTypes = watch('attachmentMimeTypes');
   const attachmentMimeTypes = watch('attachmentMimeTypes');
 
 
-  const handleSetMimeType = useCallback((disposition: 'inline' | 'attachment') => {
-    const mimeType = normalizeMimeType(currentInput);
-    if (!mimeType) return;
+  const handleSetMimeType = useCallback(
+    (disposition: 'inline' | 'attachment') => {
+      const mimeType = normalizeMimeType(currentInput);
+      if (!mimeType) return;
 
 
-    const otherDisposition = disposition === 'inline' ? 'attachment' : 'inline';
+      const otherDisposition =
+        disposition === 'inline' ? 'attachment' : 'inline';
 
 
-    const currentTargetList = watch(`${disposition}MimeTypes`);
-    const currentOtherList = watch(`${otherDisposition}MimeTypes`);
+      const currentTargetList = watch(`${disposition}MimeTypes`);
+      const currentOtherList = watch(`${otherDisposition}MimeTypes`);
 
 
-    if (!currentTargetList.includes(mimeType)) {
-      setValue(`${disposition}MimeTypes`, [...currentTargetList, mimeType], { shouldDirty: true });
-    }
+      if (!currentTargetList.includes(mimeType)) {
+        setValue(`${disposition}MimeTypes`, [...currentTargetList, mimeType], {
+          shouldDirty: true,
+        });
+      }
 
 
-    setValue(
-      `${otherDisposition}MimeTypes`,
-      currentOtherList.filter(m => m !== mimeType),
-      { shouldDirty: true },
-    );
-
-    setCurrentInput('');
-    setError(null);
-  }, [currentInput, setValue, watch]);
-
-  const handleRemove = useCallback((mimeType: string, disposition: 'inline' | 'attachment') => {
-    const currentList = watch(`${disposition}MimeTypes`);
-    setValue(
-      `${disposition}MimeTypes`,
-      currentList.filter(m => m !== mimeType),
-      { shouldDirty: true },
-    );
-  }, [setValue, watch]);
-
-  const onSubmit = async(data: ContentDispositionSettings) => {
+      setValue(
+        `${otherDisposition}MimeTypes`,
+        currentOtherList.filter((m) => m !== mimeType),
+        { shouldDirty: true },
+      );
+
+      setCurrentInput('');
+      setError(null);
+    },
+    [currentInput, setValue, watch],
+  );
+
+  const handleRemove = useCallback(
+    (mimeType: string, disposition: 'inline' | 'attachment') => {
+      const currentList = watch(`${disposition}MimeTypes`);
+      setValue(
+        `${disposition}MimeTypes`,
+        currentList.filter((m) => m !== mimeType),
+        { shouldDirty: true },
+      );
+    },
+    [setValue, watch],
+  );
+
+  const onSubmit = async (data: ContentDispositionSettingsType) => {
     try {
     try {
       setError(null);
       setError(null);
       await updateSettings(data);
       await updateSettings(data);
       reset(data);
       reset(data);
-    }
-    catch (err) {
+    } catch (err) {
       setError((err as Error).message);
       setError((err as Error).message);
     }
     }
   };
   };
@@ -124,12 +147,14 @@ const ContentDispositionSettings: React.FC = () => {
   return (
   return (
     <div className="row">
     <div className="row">
       <div className="col-12">
       <div className="col-12">
-        <h2 className="mb-4 border-0">{t('markdown_settings.content-disposition_header')}</h2>
+        <h2 className="mb-4 border-0">
+          {t('markdown_settings.content-disposition_header')}
+        </h2>
 
 
         <div className="card shadow-sm mb-4 rounded-3 border-0">
         <div className="card shadow-sm mb-4 rounded-3 border-0">
           <div className="card-body">
           <div className="card-body">
             <div className="form-group">
             <div className="form-group">
-              <label className="form-label fw-bold">
+              <label htmlFor="mime-type-input" className="form-label fw-bold">
                 {t('markdown_settings.content-disposition_options.add_header')}
                 {t('markdown_settings.content-disposition_options.add_header')}
               </label>
               </label>
               <div className="d-flex align-items-center gap-2 mb-3">
               <div className="d-flex align-items-center gap-2 mb-3">
@@ -137,7 +162,7 @@ const ContentDispositionSettings: React.FC = () => {
                   type="text"
                   type="text"
                   className="form-control rounded-3 w-50"
                   className="form-control rounded-3 w-50"
                   value={currentInput}
                   value={currentInput}
-                  onChange={e => setCurrentInput(e.target.value)}
+                  onChange={(e) => setCurrentInput(e.target.value)}
                   placeholder="e.g. image/png"
                   placeholder="e.g. image/png"
                 />
                 />
                 <button
                 <button
@@ -146,7 +171,9 @@ const ContentDispositionSettings: React.FC = () => {
                   onClick={() => handleSetMimeType('inline')}
                   onClick={() => handleSetMimeType('inline')}
                   disabled={!currentInput.trim() || isUpdating}
                   disabled={!currentInput.trim() || isUpdating}
                 >
                 >
-                  {t('markdown_settings.content-disposition_options.inline_button')}
+                  {t(
+                    'markdown_settings.content-disposition_options.inline_button',
+                  )}
                 </button>
                 </button>
                 <button
                 <button
                   className="btn btn-primary text-white px-3 flex-shrink-0 rounded-3 fw-bold"
                   className="btn btn-primary text-white px-3 flex-shrink-0 rounded-3 fw-bold"
@@ -154,7 +181,9 @@ const ContentDispositionSettings: React.FC = () => {
                   onClick={() => handleSetMimeType('attachment')}
                   onClick={() => handleSetMimeType('attachment')}
                   disabled={!currentInput.trim() || isUpdating}
                   disabled={!currentInput.trim() || isUpdating}
                 >
                 >
-                  {t('markdown_settings.content-disposition_options.attachment_button')}
+                  {t(
+                    'markdown_settings.content-disposition_options.attachment_button',
+                  )}
                 </button>
                 </button>
               </div>
               </div>
               <small className="form-text text-muted">
               <small className="form-text text-muted">
@@ -168,19 +197,31 @@ const ContentDispositionSettings: React.FC = () => {
 
 
         <div className="row">
         <div className="row">
           <MimeTypeList
           <MimeTypeList
-            title={t('markdown_settings.content-disposition_options.inline_header')}
+            title={t(
+              'markdown_settings.content-disposition_options.inline_header',
+            )}
             items={inlineMimeTypes}
             items={inlineMimeTypes}
-            emptyText={t('markdown_settings.content-disposition_options.no_inline')}
-            onRemove={m => handleRemove(m, 'inline')}
-            removeLabel={t('markdown_settings.content-disposition_options.remove_button')}
+            emptyText={t(
+              'markdown_settings.content-disposition_options.no_inline',
+            )}
+            onRemove={(m) => handleRemove(m, 'inline')}
+            removeLabel={t(
+              'markdown_settings.content-disposition_options.remove_button',
+            )}
             isUpdating={isUpdating}
             isUpdating={isUpdating}
           />
           />
           <MimeTypeList
           <MimeTypeList
-            title={t('markdown_settings.content-disposition_options.attachment_header')}
+            title={t(
+              'markdown_settings.content-disposition_options.attachment_header',
+            )}
             items={attachmentMimeTypes}
             items={attachmentMimeTypes}
-            emptyText={t('markdown_settings.content-disposition_options.no_attachment')}
-            onRemove={m => handleRemove(m, 'attachment')}
-            removeLabel={t('markdown_settings.content-disposition_options.remove_button')}
+            emptyText={t(
+              'markdown_settings.content-disposition_options.no_attachment',
+            )}
+            onRemove={(m) => handleRemove(m, 'attachment')}
+            removeLabel={t(
+              'markdown_settings.content-disposition_options.remove_button',
+            )}
             isUpdating={isUpdating}
             isUpdating={isUpdating}
           />
           />
         </div>
         </div>

+ 30 - 15
apps/app/src/client/services/admin-content-disposition.ts

@@ -1,5 +1,4 @@
 import { useCallback } from 'react';
 import { useCallback } from 'react';
-
 import useSWR from 'swr';
 import useSWR from 'swr';
 import useSWRMutation from 'swr/mutation';
 import useSWRMutation from 'swr/mutation';
 
 
@@ -27,36 +26,52 @@ interface UseContentDisposition {
   currentSettings: ContentDispositionSettings | undefined;
   currentSettings: ContentDispositionSettings | undefined;
   isLoading: boolean;
   isLoading: boolean;
   isUpdating: boolean;
   isUpdating: boolean;
-  updateSettings: (newSettings: ContentDispositionSettings) => Promise<ContentDispositionSettings>;
+  updateSettings: (
+    newSettings: ContentDispositionSettings,
+  ) => Promise<ContentDispositionSettings>;
 }
 }
 
 
 export const useContentDisposition = (): UseContentDisposition => {
 export const useContentDisposition = (): UseContentDisposition => {
   const { data, isLoading, mutate } = useSWR(
   const { data, isLoading, mutate } = useSWR(
     '/content-disposition-settings/',
     '/content-disposition-settings/',
-    endpoint => apiv3Get<ContentDispositionGetResponse>(endpoint).then(res => res.data.currentDispositionSettings),
+    (endpoint) =>
+      apiv3Get<ContentDispositionGetResponse>(endpoint).then(
+        (res) => res.data.currentDispositionSettings,
+      ),
   );
   );
 
 
   const { trigger, isMutating: isUpdating } = useSWRMutation(
   const { trigger, isMutating: isUpdating } = useSWRMutation(
     '/content-disposition-settings/',
     '/content-disposition-settings/',
-    async(endpoint: string, { arg }: { arg: ContentDispositionUpdateRequest }) => {
-      const response = await apiv3Put<ContentDispositionUpdateResponse>(endpoint, arg);
+    async (
+      endpoint: string,
+      { arg }: { arg: ContentDispositionUpdateRequest },
+    ) => {
+      const response = await apiv3Put<ContentDispositionUpdateResponse>(
+        endpoint,
+        arg,
+      );
       return response.data.currentDispositionSettings;
       return response.data.currentDispositionSettings;
     },
     },
   );
   );
 
 
-  const updateSettings = useCallback(async(newSettings: ContentDispositionSettings): Promise<ContentDispositionSettings> => {
-    const request: ContentDispositionUpdateRequest = {
-      newInlineMimeTypes: newSettings.inlineMimeTypes,
-      newAttachmentMimeTypes: newSettings.attachmentMimeTypes,
-    };
+  const updateSettings = useCallback(
+    async (
+      newSettings: ContentDispositionSettings,
+    ): Promise<ContentDispositionSettings> => {
+      const request: ContentDispositionUpdateRequest = {
+        newInlineMimeTypes: newSettings.inlineMimeTypes,
+        newAttachmentMimeTypes: newSettings.attachmentMimeTypes,
+      };
 
 
-    const updatedData = await trigger(request);
+      const updatedData = await trigger(request);
 
 
-    // Update local cache and avoid an unnecessary extra GET request
-    await mutate(updatedData, { revalidate: false });
+      // Update local cache and avoid an unnecessary extra GET request
+      await mutate(updatedData, { revalidate: false });
 
 
-    return updatedData;
-  }, [trigger, mutate]);
+      return updatedData;
+    },
+    [trigger, mutate],
+  );
 
 
   return {
   return {
     currentSettings: data,
     currentSettings: data,

+ 5 - 5
apps/app/src/server/service/file-uploader/utils/headers.spec.ts

@@ -1,9 +1,6 @@
-import {
-  vi, describe, it, expect, beforeEach,
-} from 'vitest';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
 
 
 import { configManager } from '../../config-manager';
 import { configManager } from '../../config-manager';
-
 import { determineDisposition } from './headers';
 import { determineDisposition } from './headers';
 
 
 vi.mock('../../config-manager', () => ({
 vi.mock('../../config-manager', () => ({
@@ -17,7 +14,10 @@ describe('determineDisposition', () => {
     vi.resetAllMocks();
     vi.resetAllMocks();
   });
   });
 
 
-  const setupMocks = (inlineMimeTypes: string[], attachmentMimeTypes: string[]) => {
+  const setupMocks = (
+    inlineMimeTypes: string[],
+    attachmentMimeTypes: string[],
+  ) => {
     vi.mocked(configManager.getConfig).mockImplementation(((key: string) => {
     vi.mocked(configManager.getConfig).mockImplementation(((key: string) => {
       if (key === 'attachments:contentDisposition:inlineMimeTypes') {
       if (key === 'attachments:contentDisposition:inlineMimeTypes') {
         return { inlineMimeTypes };
         return { inlineMimeTypes };

+ 10 - 5
apps/app/src/server/service/file-uploader/utils/security.ts

@@ -1,4 +1,7 @@
-export const defaultContentDispositionSettings: Record<string, 'inline' | 'attachment'> = {
+export const defaultContentDispositionSettings: Record<
+  string,
+  'inline' | 'attachment'
+> = {
   // Image Types
   // Image Types
   'image/jpeg': 'inline',
   'image/jpeg': 'inline',
   'image/png': 'inline',
   'image/png': 'inline',
@@ -31,11 +34,14 @@ export const defaultContentDispositionSettings: Record<string, 'inline' | 'attac
 
 
   // Other Common Document Formats
   // Other Common Document Formats
   'application/msword': 'attachment',
   'application/msword': 'attachment',
-  'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'attachment',
+  'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
+    'attachment',
   'application/vnd.ms-excel': 'attachment',
   'application/vnd.ms-excel': 'attachment',
-  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'attachment',
+  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
+    'attachment',
   'application/vnd.ms-powerpoint': 'attachment',
   'application/vnd.ms-powerpoint': 'attachment',
-  'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'attachment',
+  'application/vnd.openxmlformats-officedocument.presentationml.presentation':
+    'attachment',
   'application/zip': 'attachment',
   'application/zip': 'attachment',
   'application/x-rar-compressed': 'attachment',
   'application/x-rar-compressed': 'attachment',
   'text/csv': 'attachment',
   'text/csv': 'attachment',
@@ -68,7 +74,6 @@ export const strictMimeTypeSettings: Record<string, 'inline' | 'attachment'> = {
   'font/otf': 'attachment',
   'font/otf': 'attachment',
 };
 };
 
 
-
 export const laxMimeTypeSettings: Record<string, 'inline' | 'attachment'> = {
 export const laxMimeTypeSettings: Record<string, 'inline' | 'attachment'> = {
   // Documents
   // Documents
   'application/pdf': 'inline',
   'application/pdf': 'inline',