From a2e02efa2c828b762919c147f2c86228e9e647ca Mon Sep 17 00:00:00 2001 From: David Hordiienko Date: Wed, 24 Sep 2025 10:26:27 +0300 Subject: [PATCH 01/48] improve: sentry initialization --- backend/src/index.ts | 1 + frontend/src/api.gen/client.gen.ts | 12 ++++++------ frontend/src/api.gen/index.ts | 2 +- frontend/src/api.gen/sdk.gen.ts | 4 ++-- frontend/src/api.gen/types.gen.ts | 8 ++++---- frontend/src/lib/sentry.ts | 16 ++++------------ frontend/src/main.tsx | 4 ++-- 7 files changed, 20 insertions(+), 27 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 080d3a5a7..49c314d0b 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -26,6 +26,7 @@ docs(app); Sentry.init({ enabled: !!appConfig.sentryDsn, dsn: appConfig.sentryDsn, + debug: appConfig.debug, environment: appConfig.mode, integrations: [nodeProfilingIntegration()], // Tracing to capture 100% of transactions diff --git a/frontend/src/api.gen/client.gen.ts b/frontend/src/api.gen/client.gen.ts index 71590bf9f..eabdfc7f0 100644 --- a/frontend/src/api.gen/client.gen.ts +++ b/frontend/src/api.gen/client.gen.ts @@ -1,8 +1,8 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClientConfig } from '../api-config'; -import { type ClientOptions, type Config, createClient, createConfig } from './client'; -import type { ClientOptions as ClientOptions2 } from './types.gen'; +import { type Config, createClient, createConfig, type ClientOptions as DefaultClientOptions } from './client'; +import type { ClientOptions } from './types.gen'; /** * The `createClientConfig()` function will be called on client initialization @@ -12,13 +12,13 @@ import type { ClientOptions as ClientOptions2 } from './types.gen'; * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = ( - override?: Config, -) => Config & T>; +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; export const client = createClient( createClientConfig( - createConfig({ + createConfig({ baseUrl: 'http://localhost:4000', throwOnError: true, }), diff --git a/frontend/src/api.gen/index.ts b/frontend/src/api.gen/index.ts index 57ed02bf5..f796d2cc8 100644 --- a/frontend/src/api.gen/index.ts +++ b/frontend/src/api.gen/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts export * from './sdk.gen'; -export type * from './types.gen'; +export * from './types.gen'; diff --git a/frontend/src/api.gen/sdk.gen.ts b/frontend/src/api.gen/sdk.gen.ts index 38e011960..d2028bd8b 100644 --- a/frontend/src/api.gen/sdk.gen.ts +++ b/frontend/src/api.gen/sdk.gen.ts @@ -1,6 +1,6 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Client, Options as Options2, TDataShape } from './client'; +import type { Client, Options as ClientOptions, TDataShape } from './client'; import { client } from './client.gen'; import type { AcceptEntityInviteData, @@ -210,7 +210,7 @@ import type { VerifyEmailErrors, } from './types.gen'; -export type Options = Options2 & { +export type Options = ClientOptions & { /** * You can provide a client instance returned by `createClient()` instead of * individual options. This might be also useful if you want to implement a diff --git a/frontend/src/api.gen/types.gen.ts b/frontend/src/api.gen/types.gen.ts index 8590b4f30..11fc47d3c 100644 --- a/frontend/src/api.gen/types.gen.ts +++ b/frontend/src/api.gen/types.gen.ts @@ -1,9 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -export type ClientOptions = { - baseUrl: 'http://localhost:4000' | (string & {}); -}; - export type User = { createdAt: string; id: string; @@ -4281,3 +4277,7 @@ export type ResendInvitationResponses = { }; export type ResendInvitationResponse = ResendInvitationResponses[keyof ResendInvitationResponses]; + +export type ClientOptions = { + baseUrl: 'http://localhost:4000' | (string & {}); +}; diff --git a/frontend/src/lib/sentry.ts b/frontend/src/lib/sentry.ts index b3650ce3f..fb38eb03a 100644 --- a/frontend/src/lib/sentry.ts +++ b/frontend/src/lib/sentry.ts @@ -1,23 +1,15 @@ import * as Sentry from '@sentry/react'; import { appConfig } from 'config'; -// Initialize Sentry -window.ononline = () => { - initSentry(); -}; - -// Close Sentry when offline to avoid sending errors -window.onoffline = () => { - console.info('You went offline. Closing Sentry.'); - Sentry.close(); -}; - export const initSentry = () => { + if (!appConfig.sentryDsn) return; + // Send errors to Sentry Sentry.init({ - enabled: !!appConfig.sentryDsn, dsn: appConfig.sentryDsn, + debug: appConfig.debug, environment: appConfig.mode, + transport: Sentry.makeBrowserOfflineTransport(Sentry.makeFetchTransport), // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring. tracesSampleRate: 1.0, // Set `tracePropagationTargets` to control for which URLs distributed tracing should be enabled diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index b23ee5347..1e6abd44c 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -25,8 +25,8 @@ renderAscii(); // Add badge to favicon based on config mode addBadgeToFavicon(appConfig.mode as ConfigMode); -// Initialize Sentry if online -if (navigator.onLine) initSentry(); +// Initialize Sentry +initSentry(); ReactDOM.createRoot(root).render( From 4d5eeb86c2c5168621aa92920f27a3611eade1f7 Mon Sep 17 00:00:00 2001 From: David Hordiienko Date: Wed, 24 Sep 2025 12:21:39 +0300 Subject: [PATCH 02/48] fix: offline name update --- .../attachments/helpers/local-file-storage.ts | 30 +++++++++++++++++++ .../{ => hooks}/use-attachment-url.tsx | 0 .../use-electric-sync-attachments.ts | 0 .../use-local-sync-attachments.tsx | 0 .../use-merge-local-attachments.ts | 10 ++++--- .../modules/attachments/query-mutations.ts | 30 ++++++++++++++++++- .../src/modules/attachments/render/index.tsx | 2 +- .../src/modules/attachments/table/index.tsx | 8 +++-- .../attachments/table/preview/index.tsx | 2 +- frontend/src/modules/attachments/types.ts | 2 +- 10 files changed, 73 insertions(+), 11 deletions(-) rename frontend/src/modules/attachments/{ => hooks}/use-attachment-url.tsx (100%) rename frontend/src/modules/attachments/{ => hooks}/use-electric-sync-attachments.ts (100%) rename frontend/src/modules/attachments/{ => hooks}/use-local-sync-attachments.tsx (100%) rename frontend/src/modules/attachments/{ => hooks}/use-merge-local-attachments.ts (89%) diff --git a/frontend/src/modules/attachments/helpers/local-file-storage.ts b/frontend/src/modules/attachments/helpers/local-file-storage.ts index 4402cef72..6bdc31458 100644 --- a/frontend/src/modules/attachments/helpers/local-file-storage.ts +++ b/frontend/src/modules/attachments/helpers/local-file-storage.ts @@ -84,6 +84,36 @@ export const LocalFileStorage = { } }, + async updateFileName(fileId: string, newName: string): Promise { + try { + const storageKeys = await keys(); + if (!storageKeys.length) return undefined; + + for (const groupKey of storageKeys) { + const group = await get(groupKey); + if (!group || !group.files) continue; + + const file = group.files[fileId]; + if (file) { + // Update the name + const updatedFile = { ...file, name: newName }; + group.files[fileId] = updatedFile; + + // Save back to IndexedDB + await set(groupKey, group); + + return updatedFile; + } + } + + return undefined; // file not found + } catch (error) { + Sentry.captureException(error); + console.error(`Failed to update file name (${fileId}):`, error); + return undefined; + } + }, + async removeFiles(fileIds: string[]): Promise { try { const storageKeys = await keys(); diff --git a/frontend/src/modules/attachments/use-attachment-url.tsx b/frontend/src/modules/attachments/hooks/use-attachment-url.tsx similarity index 100% rename from frontend/src/modules/attachments/use-attachment-url.tsx rename to frontend/src/modules/attachments/hooks/use-attachment-url.tsx diff --git a/frontend/src/modules/attachments/use-electric-sync-attachments.ts b/frontend/src/modules/attachments/hooks/use-electric-sync-attachments.ts similarity index 100% rename from frontend/src/modules/attachments/use-electric-sync-attachments.ts rename to frontend/src/modules/attachments/hooks/use-electric-sync-attachments.ts diff --git a/frontend/src/modules/attachments/use-local-sync-attachments.tsx b/frontend/src/modules/attachments/hooks/use-local-sync-attachments.tsx similarity index 100% rename from frontend/src/modules/attachments/use-local-sync-attachments.tsx rename to frontend/src/modules/attachments/hooks/use-local-sync-attachments.tsx diff --git a/frontend/src/modules/attachments/use-merge-local-attachments.ts b/frontend/src/modules/attachments/hooks/use-merge-local-attachments.ts similarity index 89% rename from frontend/src/modules/attachments/use-merge-local-attachments.ts rename to frontend/src/modules/attachments/hooks/use-merge-local-attachments.ts index 650d983cd..afcebd677 100644 --- a/frontend/src/modules/attachments/use-merge-local-attachments.ts +++ b/frontend/src/modules/attachments/hooks/use-merge-local-attachments.ts @@ -9,6 +9,7 @@ import { formatUpdatedCacheData, getQueryItems } from '~/query/utils/mutate-quer import { nanoid } from '~/utils/nanoid'; const limit = appConfig.requestLimits.attachments; + export const useMergeLocalAttachments = (organizationId: string, { q, sort, order }: AttachmentsRouteSearchParams) => { const { getData: fetchStoredFiles } = LocalFileStorage; @@ -26,15 +27,16 @@ export const useMergeLocalAttachments = (organizationId: string, { q, sort, orde const groupId = files.length > 1 ? nanoid() : null; - const localAttachments: Attachment[] = files.map(({ size, preview, id, type, data, meta }) => ({ + // TODO(IMPROVE)local file info(add createdAt/By, groupId into the file?) + const localAttachments: Attachment[] = files.map(({ size, preview, id, type, data, name, meta }) => ({ id, - size: size ? String(size) : String(data.size), + size: String(size || data.size), url: preview || '', thumbnailUrl: null, convertedUrl: null, contentType: type, convertedContentType: null, - name: meta.name, + name: name || meta.name, // to handle offline name update, not updating orig fileName from meta public: meta.public, bucketName: meta.bucketName, entityType: 'attachment', @@ -43,7 +45,7 @@ export const useMergeLocalAttachments = (organizationId: string, { q, sort, orde modifiedAt: null, modifiedBy: null, groupId, - filename: meta?.name || 'Unnamed file', + filename: meta.name || 'Unnamed file', organizationId, })); diff --git a/frontend/src/modules/attachments/query-mutations.ts b/frontend/src/modules/attachments/query-mutations.ts index 2a6f2d389..9cdf8d5b1 100644 --- a/frontend/src/modules/attachments/query-mutations.ts +++ b/frontend/src/modules/attachments/query-mutations.ts @@ -157,7 +157,35 @@ export const useAttachmentCreateMutation = () => export const useAttachmentUpdateMutation = () => useMutation({ mutationKey: attachmentsKeys.update, - mutationFn: async ({ id, orgIdOrSlug, ...body }) => { + mutationFn: async ({ id, orgIdOrSlug, localUpdate, ...body }) => { + if (localUpdate && body.name) { + const file = await LocalFileStorage.updateFileName(id, body.name); + + if (!file) throw new Error(`Failed to update file name (${id}):`); + + // TODO(IMPROVE)offline update responce(add createdAt/By, groupId into the file?) + const localAttachment: Attachment = { + id: file.id, + size: String(file.data.size ?? 0), + url: file.preview || '', + thumbnailUrl: null, + convertedUrl: null, + contentType: file.type, + convertedContentType: null, + name: file.name || file.meta.name, + public: file.meta.public ?? false, + bucketName: file.meta.bucketName, + entityType: 'attachment', + createdAt: new Date().toISOString(), + createdBy: null, + modifiedAt: new Date().toISOString(), + modifiedBy: null, + groupId: '', + filename: file.meta.name || 'Unnamed file', + organizationId: orgIdOrSlug, + }; + return localAttachment; + } return await updateAttachment({ body, path: { id, orgIdOrSlug } }); }, onMutate: async (variables: UpdateAttachmentParams) => { diff --git a/frontend/src/modules/attachments/render/index.tsx b/frontend/src/modules/attachments/render/index.tsx index ecabbd113..a6f76ea79 100644 --- a/frontend/src/modules/attachments/render/index.tsx +++ b/frontend/src/modules/attachments/render/index.tsx @@ -1,6 +1,6 @@ import { lazy, Suspense } from 'react'; import { useBreakpoints } from '~/hooks/use-breakpoints'; -import { useAttachmentUrl } from '~/modules/attachments/use-attachment-url'; +import { useAttachmentUrl } from '~/modules/attachments/hooks/use-attachment-url'; import Spinner from '~/modules/common/spinner'; // Lazy-loaded components diff --git a/frontend/src/modules/attachments/table/index.tsx b/frontend/src/modules/attachments/table/index.tsx index b9eb84cba..fbae45c8e 100644 --- a/frontend/src/modules/attachments/table/index.tsx +++ b/frontend/src/modules/attachments/table/index.tsx @@ -7,17 +7,18 @@ import { useTranslation } from 'react-i18next'; import type { Attachment } from '~/api.gen'; import useOfflineTableSearch from '~/hooks/use-offline-table-search'; import useSearchParams from '~/hooks/use-search-params'; +import { useElectricSyncAttachments } from '~/modules/attachments/hooks/use-electric-sync-attachments'; +import { useLocalSyncAttachments } from '~/modules/attachments/hooks/use-local-sync-attachments'; +import { useMergeLocalAttachments } from '~/modules/attachments/hooks/use-merge-local-attachments'; import { attachmentsQueryOptions } from '~/modules/attachments/query'; import { useAttachmentUpdateMutation } from '~/modules/attachments/query-mutations'; import { AttachmentsTableBar } from '~/modules/attachments/table/bar'; import { useColumns } from '~/modules/attachments/table/columns'; -import { useElectricSyncAttachments } from '~/modules/attachments/use-electric-sync-attachments'; -import { useLocalSyncAttachments } from '~/modules/attachments/use-local-sync-attachments'; -import { useMergeLocalAttachments } from '~/modules/attachments/use-merge-local-attachments'; import ContentPlaceholder from '~/modules/common/content-placeholder'; import { DataTable } from '~/modules/common/data-table'; import { useSortColumns } from '~/modules/common/data-table/sort-columns'; import type { EntityPage } from '~/modules/entities/types'; +import { isCDNUrl } from '~/utils/is-cdn-url'; import type { AttachmentsRouteSearchParams } from '../types'; const LIMIT = appConfig.requestLimits.attachments; @@ -80,6 +81,7 @@ const AttachmentsTable = ({ entity, canUpload = true, isSheet = false }: Attachm id: attachment.id, orgIdOrSlug: entity.id, name: attachment.name, + localUpdate: !isCDNUrl(attachment.url), }); } }; diff --git a/frontend/src/modules/attachments/table/preview/index.tsx b/frontend/src/modules/attachments/table/preview/index.tsx index 6bfdd6605..8d8c57e3f 100644 --- a/frontend/src/modules/attachments/table/preview/index.tsx +++ b/frontend/src/modules/attachments/table/preview/index.tsx @@ -1,5 +1,5 @@ +import { useAttachmentUrl } from '~/modules/attachments/hooks/use-attachment-url'; import FilePlaceholder from '~/modules/attachments/table/preview/placeholder'; -import { useAttachmentUrl } from '~/modules/attachments/use-attachment-url'; interface Props { id: string; diff --git a/frontend/src/modules/attachments/types.ts b/frontend/src/modules/attachments/types.ts index d91408c1b..538347ee9 100644 --- a/frontend/src/modules/attachments/types.ts +++ b/frontend/src/modules/attachments/types.ts @@ -12,5 +12,5 @@ export type AttachmentInfiniteQueryData = InfiniteQueryData; export type AttachmentContextProp = ContextQueryProp; export type CreateAttachmentParams = { localCreation: boolean; attachments: CreateAttachmentData['body'] } & CreateAttachmentData['path']; -export type UpdateAttachmentParams = UpdateAttachmentData['body'] & UpdateAttachmentData['path']; +export type UpdateAttachmentParams = UpdateAttachmentData['body'] & UpdateAttachmentData['path'] & { localUpdate: boolean }; export type DeleteAttachmentsParams = { localDeletionIds: string[]; serverDeletionIds: string[] } & DeleteAttachmentsData['path']; From 6ed7bea6229478b01d158526813ba2bc94f57ad3 Mon Sep 17 00:00:00 2001 From: David Hordiienko Date: Wed, 24 Sep 2025 12:53:14 +0300 Subject: [PATCH 03/48] fix: uppy preview --- frontend/src/api.gen/client.gen.ts | 12 ++++++------ frontend/src/api.gen/index.ts | 2 +- frontend/src/api.gen/sdk.gen.ts | 4 ++-- frontend/src/api.gen/types.gen.ts | 8 ++++---- .../attachments/hooks/use-local-sync-attachments.tsx | 1 + .../src/modules/common/uploader/helpers/index.ts | 4 ---- 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/frontend/src/api.gen/client.gen.ts b/frontend/src/api.gen/client.gen.ts index eabdfc7f0..71590bf9f 100644 --- a/frontend/src/api.gen/client.gen.ts +++ b/frontend/src/api.gen/client.gen.ts @@ -1,8 +1,8 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClientConfig } from '../api-config'; -import { type Config, createClient, createConfig, type ClientOptions as DefaultClientOptions } from './client'; -import type { ClientOptions } from './types.gen'; +import { type ClientOptions, type Config, createClient, createConfig } from './client'; +import type { ClientOptions as ClientOptions2 } from './types.gen'; /** * The `createClientConfig()` function will be called on client initialization @@ -12,13 +12,13 @@ import type { ClientOptions } from './types.gen'; * `setConfig()`. This is useful for example if you're using Next.js * to ensure your client always has the correct values. */ -export type CreateClientConfig = ( - override?: Config, -) => Config & T>; +export type CreateClientConfig = ( + override?: Config, +) => Config & T>; export const client = createClient( createClientConfig( - createConfig({ + createConfig({ baseUrl: 'http://localhost:4000', throwOnError: true, }), diff --git a/frontend/src/api.gen/index.ts b/frontend/src/api.gen/index.ts index f796d2cc8..57ed02bf5 100644 --- a/frontend/src/api.gen/index.ts +++ b/frontend/src/api.gen/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts export * from './sdk.gen'; -export * from './types.gen'; +export type * from './types.gen'; diff --git a/frontend/src/api.gen/sdk.gen.ts b/frontend/src/api.gen/sdk.gen.ts index d2028bd8b..38e011960 100644 --- a/frontend/src/api.gen/sdk.gen.ts +++ b/frontend/src/api.gen/sdk.gen.ts @@ -1,6 +1,6 @@ // This file is auto-generated by @hey-api/openapi-ts -import type { Client, Options as ClientOptions, TDataShape } from './client'; +import type { Client, Options as Options2, TDataShape } from './client'; import { client } from './client.gen'; import type { AcceptEntityInviteData, @@ -210,7 +210,7 @@ import type { VerifyEmailErrors, } from './types.gen'; -export type Options = ClientOptions & { +export type Options = Options2 & { /** * You can provide a client instance returned by `createClient()` instead of * individual options. This might be also useful if you want to implement a diff --git a/frontend/src/api.gen/types.gen.ts b/frontend/src/api.gen/types.gen.ts index 11fc47d3c..8590b4f30 100644 --- a/frontend/src/api.gen/types.gen.ts +++ b/frontend/src/api.gen/types.gen.ts @@ -1,5 +1,9 @@ // This file is auto-generated by @hey-api/openapi-ts +export type ClientOptions = { + baseUrl: 'http://localhost:4000' | (string & {}); +}; + export type User = { createdAt: string; id: string; @@ -4277,7 +4281,3 @@ export type ResendInvitationResponses = { }; export type ResendInvitationResponse = ResendInvitationResponses[keyof ResendInvitationResponses]; - -export type ClientOptions = { - baseUrl: 'http://localhost:4000' | (string & {}); -}; diff --git a/frontend/src/modules/attachments/hooks/use-local-sync-attachments.tsx b/frontend/src/modules/attachments/hooks/use-local-sync-attachments.tsx index 2815a489d..56b55d1f5 100644 --- a/frontend/src/modules/attachments/hooks/use-local-sync-attachments.tsx +++ b/frontend/src/modules/attachments/hooks/use-local-sync-attachments.tsx @@ -8,6 +8,7 @@ import type { AttachmentToInsert } from '~/modules/attachments/types'; import { createBaseTransloaditUppy } from '~/modules/common/uploader/helpers'; import type { UploadedUppyFile } from '~/modules/common/uploader/types'; +// TODO(improvement) make uploaded attachment naming right, if it was changed during offline update it on upload export const useLocalSyncAttachments = (organizationId: string) => { const { isOnline } = useOnlineManager(); const { mutate: createAttachments } = useAttachmentCreateMutation(); diff --git a/frontend/src/modules/common/uploader/helpers/index.ts b/frontend/src/modules/common/uploader/helpers/index.ts index d9bc84f1d..7f59ab596 100644 --- a/frontend/src/modules/common/uploader/helpers/index.ts +++ b/frontend/src/modules/common/uploader/helpers/index.ts @@ -69,9 +69,5 @@ export const createBaseTransloaditUppy = async (uppyOptions: CustomUppyOpt, toke const onBeforeFileAdded = (file: CustomUppyFile) => { // Simplify file ID and add content type to meta file.id = nanoid(); - - // Generate preview for all file types using blob URL - if (!file.preview && file.data instanceof Blob) file.preview = URL.createObjectURL(file.data); - return file; }; From 0fbfc3861c3344100b8989b98272b84ca666e4d0 Mon Sep 17 00:00:00 2001 From: David Hordiienko Date: Wed, 24 Sep 2025 14:15:58 +0300 Subject: [PATCH 04/48] fix: preview render --- .../src/modules/attachments/hooks/use-attachment-url.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/modules/attachments/hooks/use-attachment-url.tsx b/frontend/src/modules/attachments/hooks/use-attachment-url.tsx index 4d113ecba..4bca8509c 100644 --- a/frontend/src/modules/attachments/hooks/use-attachment-url.tsx +++ b/frontend/src/modules/attachments/hooks/use-attachment-url.tsx @@ -31,9 +31,10 @@ export const useAttachmentUrl = (id: string, baseUrl: string, type: string) => { useEffect(() => { isMounted.current = true; - // Use direct URL for static images either for remote URL + // Use direct URL for static images or remote CDN URL if (sanitizedUrl.startsWith('/static/') || isCDNUrl(sanitizedUrl)) { setUrl(sanitizedUrl); + setError(null); // Clear any previous error return; } @@ -41,28 +42,32 @@ export const useAttachmentUrl = (id: string, baseUrl: string, type: string) => { const cachedUrl = useBlobStore.getState().getBlobUrl(id); if (cachedUrl) { setUrl(cachedUrl); + setError(null); // Clear any previous error return; } - // If URL is not a remote static path, we assume it's a local file const fetchLocal = async () => { try { const file = await LocalFileStorage.getFile(id); if (!file) { setError(i18n.t('error:local_file_not_found')); + setUrl(null); // Make sure URL is null if file not found return; } + if (isMounted.current) { const blob = new Blob([file.data], { type: type || 'application/octet-stream' }); const objectUrl = URL.createObjectURL(blob); useBlobStore.getState().setBlobUrl(id, objectUrl); setUrl(objectUrl); + setError(null); // Clear error on success } } catch (e) { console.error(e); if (e instanceof Error) { Sentry.captureException(e); setError(`Failed to load file: ${e.message}`); + setUrl(null); // Ensure URL is null on error } } }; From 2e88f0e5af8f62299b561fa4ce671404d739e0a1 Mon Sep 17 00:00:00 2001 From: flipvanhaaren Date: Wed, 24 Sep 2025 15:20:55 +0200 Subject: [PATCH 05/48] progress: auth code improvements --- backend/emails/email-verification.tsx | 5 +- backend/emails/oauth-verification.tsx | 45 +++++++++++ backend/src/db/schema/oauth-accounts.ts | 6 +- backend/src/lib/mailer.ts | 4 +- backend/src/middlewares/has-valid-token.ts | 4 +- backend/src/middlewares/is-no-bot.ts | 4 +- .../src/middlewares/rate-limiter/limiters.ts | 6 +- backend/src/modules/attachments/routes.ts | 8 ++ backend/src/modules/auth/handlers.ts | 50 +++++++------ .../oauth/{callback-flow.ts => callback.ts} | 53 +++++++------ .../oauth/{session.ts => initiation.ts} | 71 +++++++++--------- .../auth/helpers/send-verification-email.ts | 23 ++++-- backend/src/modules/auth/routes.ts | 29 +++---- backend/src/modules/auth/schema.ts | 7 +- backend/src/modules/me/handlers.ts | 6 +- backend/src/modules/me/routes.ts | 75 +++++++++---------- backend/src/modules/system/routes.ts | 1 + backend/src/utils/validate-token.ts | 33 +++++--- config/default.ts | 2 +- frontend/src/api.gen/sdk.gen.ts | 42 ++++++----- frontend/src/api.gen/types.gen.ts | 23 +++--- frontend/src/api.gen/zod.gen.ts | 15 ++-- .../src/modules/auth/accept-entity-invite.tsx | 5 +- .../src/modules/auth/email-verification.tsx | 7 +- frontend/src/modules/auth/types.ts | 4 +- frontend/src/modules/auth/use-token-check.tsx | 12 +-- frontend/src/modules/me/settings-page.tsx | 2 +- .../src/routes/{auth.tsx => auth-routes.tsx} | 16 +++- .../src/routes/{base.tsx => base-routes.tsx} | 0 .../src/routes/{home.tsx => home-routes.tsx} | 2 +- .../{marketing.tsx => marketing-routes.tsx} | 2 +- ...anizations.tsx => organization-routes.tsx} | 2 +- frontend/src/routes/route-tree.tsx | 14 ++-- .../routes/{system.tsx => system-routes.tsx} | 2 +- .../src/routes/{users.tsx => user-routes.tsx} | 2 +- locales/en/backend.json | 6 +- locales/en/common.json | 4 +- locales/en/errors.json | 13 ++-- 38 files changed, 364 insertions(+), 241 deletions(-) create mode 100644 backend/emails/oauth-verification.tsx rename backend/src/modules/auth/helpers/oauth/{callback-flow.ts => callback.ts} (88%) rename backend/src/modules/auth/helpers/oauth/{session.ts => initiation.ts} (65%) rename frontend/src/routes/{auth.tsx => auth-routes.tsx} (90%) rename frontend/src/routes/{base.tsx => base-routes.tsx} (100%) rename frontend/src/routes/{home.tsx => home-routes.tsx} (96%) rename frontend/src/routes/{marketing.tsx => marketing-routes.tsx} (95%) rename frontend/src/routes/{organizations.tsx => organization-routes.tsx} (98%) rename frontend/src/routes/{system.tsx => system-routes.tsx} (98%) rename frontend/src/routes/{users.tsx => user-routes.tsx} (98%) diff --git a/backend/emails/email-verification.tsx b/backend/emails/email-verification.tsx index 63de04942..77a31f41f 100644 --- a/backend/emails/email-verification.tsx +++ b/backend/emails/email-verification.tsx @@ -12,11 +12,12 @@ import { Footer } from './components/footer'; const appName = appConfig.name; export interface EmailVerificationEmailProps extends BasicTemplateType { + name: string; verificationLink: string; email: string; } -export const EmailVerificationEmail = ({ lng, verificationLink, email }: EmailVerificationEmailProps) => { +export const EmailVerificationEmail = ({ lng, verificationLink, email, name }: EmailVerificationEmailProps) => { return ( @@ -25,7 +26,7 @@ export const EmailVerificationEmail = ({ lng, verificationLink, email }: EmailVe diff --git a/backend/emails/oauth-verification.tsx b/backend/emails/oauth-verification.tsx new file mode 100644 index 000000000..0f5bd7c7f --- /dev/null +++ b/backend/emails/oauth-verification.tsx @@ -0,0 +1,45 @@ +import { appConfig } from 'config'; +import i18n from 'i18next'; +import { Text } from 'jsx-email'; +import type { BasicTemplateType } from '../src/lib/mailer'; +import { AppLogo } from './components/app-logo'; +import { EmailContainer } from './components/container'; +import { EmailBody } from './components/email-body'; +import { EmailButton } from './components/email-button'; +import { EmailHeader } from './components/email-header'; +import { Footer } from './components/footer'; + +const appName = appConfig.name; + +export interface OAuthVerificationEmailProps extends BasicTemplateType { + name: string; + verificationLink: string; + email: string; + providerEmail: string; + providerName: string; +} + +export const OAuthVerificationEmail = ({ lng, verificationLink, email, name, providerEmail, providerName }: OAuthVerificationEmailProps) => { + return ( + + + + + + + + + + +