feat(condo): DOMA-13015 files for news (client-side)#7465
feat(condo): DOMA-13015 files for news (client-side)#7465
Conversation
📝 WalkthroughWalkthroughThis PR introduces comprehensive file upload and management functionality to the news domain. It adds React components for file selection, preview, and display; extends GraphQL operations for creating and updating file records; modifies access control to permit null newsItem references during initial file creation; updates the news form to handle file uploads and attachment; and adds client-side file processing (format conversion via FFmpeg, canvas-based thumbnails). Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant FilesUploadList
participant FileConverter
participant S3API as S3 API
participant GraphQL
participant Apollo as Apollo Cache
User->>FilesUploadList: Select file(s)
FilesUploadList->>FileConverter: Convert format if needed<br/>(HEIC→JPEG, WebP→JPEG,<br/>MOV/MP4→MP4 via FFmpeg)
FileConverter->>FileConverter: Generate thumbnail<br/>(canvas-based)
FileConverter-->>FilesUploadList: Conversion complete
FilesUploadList->>S3API: Upload converted file
S3API-->>FilesUploadList: File URL + metadata
FilesUploadList->>GraphQL: createNewsItemFile mutation<br/>(file, organization, user)
GraphQL-->>FilesUploadList: Created record (id, file)
FilesUploadList->>Apollo: Update local state +<br/>dispatch add action
Apollo-->>User: File rendered in preview
User->>User: Complete form & submit
User->>GraphQL: Create/update NewsItem
GraphQL-->>GraphQL: Return newsItemId
GraphQL->>GraphQL: For each added file:<br/>updateNewsItemFile<br/>(newsItemId)
GraphQL->>GraphQL: For each deleted file:<br/>updateNewsItemFile<br/>(deletedAt timestamp)
GraphQL-->>Apollo: Evict allNewsItemFiles cache
Apollo->>Apollo: Garbage collect
Apollo-->>User: Form complete
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Stylelint (17.6.0)apps/condo/domains/news/components/FilesUploadList.module.cssConfigurationError: Could not find "stylelint-config-rational-order". Do you need to install the package or use the "configBasedir" option? Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7999df9e32
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| return await checkPermissionsInEmployedOrRelatedOrganizations(context, user, organizationId, 'canManageNewsItems') | ||
| } | ||
| return false | ||
| return true |
There was a problem hiding this comment.
Enforce org permission when creating detached news files
Returning true here lets any authenticated STAFF user create a NewsItemFile without newsItem/organization checks. Because updates on organization-less files are later authorized by createdBy alone in canManageNewsItemFiles, a staff user without canManageNewsItems can create an orphan file and then attach it to a news item ID they know, bypassing organization-level manage permission checks.
Useful? React with 👍 / 👎.
| id: resolvedData?.[fieldPath]?.id || null, | ||
| }) | ||
| const mimetype = fileRecord?.fileMimeType | ||
| const [normalizedMimetype] = mimetype.replaceAll(' ', '').split(';') |
There was a problem hiding this comment.
Check mimetype exists before normalizing it
This calls replaceAll on mimetype before verifying it is defined. If FileRecord is missing or has no fileMimeType, the hook throws a runtime TypeError instead of the intended UNKNOWN_FILE_TYPE GraphQL error, turning a handled validation case into a server error.
Useful? React with 👍 / 👎.
| setIsLoading(true) | ||
| const newsItemFiles = await getNewsItemFiles({ | ||
| variables: { |
There was a problem hiding this comment.
Reset reupload loading state when file copy fails
reuploadFiles sets isLoading to true and then awaits several network/file operations without try/finally. If any fetch/upload/create call rejects, setIsLoading(false) is never reached, so the edit flow can remain stuck in the loading state (LOADING!!!) and users cannot continue.
Useful? React with 👍 / 👎.
|
There was a problem hiding this comment.
Actionable comments posted: 20
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
apps/condo/domains/news/access/NewsItemFile.js (1)
70-81:⚠️ Potential issue | 🟡 MinorImplement cleanup mechanism for temporary orphaned NewsItemFile records.
The access control allows STAFF users to create
NewsItemFilerecords without a linkednewsItem(line 81 returnstrue), enabling temporary uploads during form creation. However, no cleanup job or TTL mechanism exists to remove abandoned files that remain unlinked after a reasonable period. Without cleanup, orphanedNewsItemFilerecords will accumulate indefinitely, consuming database storage and associated file storage.Implement an automated cleanup process to soft-delete
NewsItemFilerecords that meet both conditions:
newsItemis null (not linked to any news item)- Created more than a configurable TTL ago (e.g., 24-48 hours)
The cleanup can be implemented as a scheduled task using the existing task infrastructure or as a background job, similar to the pattern in
notifyResidentsAboutNewsItem.js.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/condo/domains/news/access/NewsItemFile.js` around lines 70 - 81, Add a scheduled cleanup job that soft-deletes orphaned NewsItemFile rows (where newsItem is null) older than a configurable TTL (e.g., NEWS_ITEM_FILE_TTL); create a task module (e.g., cleanupOrphanedNewsItemFiles.js) that queries the NewsItemFile model for records with newsItem == null and createdAt < now - TTL, then soft-delete them using the same soft-delete method/pattern your app uses (e.g., set deletedAt or call the existing softDelete helper) and wire the task into the existing scheduler similar to notifyResidentsAboutNewsItem.js; expose TTL via config/env and ensure the task logs what it deletes and is idempotent so it can run regularly.apps/condo/domains/news/schema/NewsItemFile.js (1)
99-106:⚠️ Potential issue | 🟠 MajorGuard
mimetypebefore callingreplaceAll.If
FileRecordis missing orfileMimeTypeis empty, Line 103 throws a plainTypeErrorbefore yourUNKNOWN_FILE_TYPEbranch runs. That turns invalid user input into a 500.Proposed fix
const fileRecord = await getByCondition('FileRecord', { id: resolvedData?.[fieldPath]?.id || null, }) const mimetype = fileRecord?.fileMimeType - const [normalizedMimetype] = mimetype.replaceAll(' ', '').split(';') - - if (!normalizedMimetype) { + if (!mimetype) { throw new GQLError(ERRORS.UNKNOWN_FILE_TYPE, context) } + const [normalizedMimetype] = mimetype.replaceAll(' ', '').split(';') if (!ALLOWED_MIME_TYPES.includes(normalizedMimetype)) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/condo/domains/news/schema/NewsItemFile.js` around lines 99 - 106, The code calls mimetype.replaceAll without guarding mimetype, causing a TypeError when FileRecord or fileMimeType is missing; before splitting, check that fileRecord?.fileMimeType (the mimetype variable) is defined/truthy and if not throw the existing GQLError using ERRORS.UNKNOWN_FILE_TYPE with context; then compute normalizedMimetype (e.g., after guarding, call replaceAll and split) and proceed—update the block around getByCondition('FileRecord'), mimetype, normalizedMimetype, and the GQLError throw to perform the presence check before calling replaceAll.apps/condo/domains/news/components/NewsForm/BaseNewsForm.tsx (1)
628-878:⚠️ Potential issue | 🔴 CriticalAdd missing dependencies to
handleFormSubmituseCallback.This callback reads
selectAppsFormValues,condoFormValues,sharingAppsFormValues,modifiedFiles,updateNewsItemFile,client,getSelectedAndNotSkippedSharingApps, andselectedProperties, but none are included in the dependency list. This causes the callback to capture stale closures: the initial form state values (often null) prevent proper submission at line 330, file operations at lines 350 and 365 use stale file lists, sharing configs at line 382 use outdated parameters, and property lookups at line 443 reference wrong properties. Add these dependencies to the array at line 878.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/condo/domains/news/components/NewsForm/BaseNewsForm.tsx` around lines 628 - 878, The useCallback for handleFormSubmit is missing several dependencies which causes stale closures; update the dependency array of handleFormSubmit to include selectAppsFormValues, condoFormValues, sharingAppsFormValues, modifiedFiles, updateNewsItemFile, client, getSelectedAndNotSkippedSharingApps, selectedProperties (in addition to the existing ones like actionName, createOrUpdateNewsItem, updateNewsItem, etc.) so the callback sees current form values, file lists, sharing configs and property data when it runs.
🧹 Nitpick comments (8)
apps/condo/domains/news/components/FilesPreview/DocumentsPreview.tsx (2)
7-7: Add TypeScript types for thefilesprop.The
filesparameter lacks type annotation, resulting in implicitany. This reduces type safety and IDE support. Based on learnings, prefer type aliases for object shapes.♻️ Proposed fix to add types
+type FileItem = { + id?: string + uid?: string + response?: { + originalName?: string + } +} + +type DocumentsPreviewProps = { + files?: FileItem[] +} + -export const DocumentsPreview = ({ files }) => { +export const DocumentsPreview: React.FC<DocumentsPreviewProps> = ({ files }) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/condo/domains/news/components/FilesPreview/DocumentsPreview.tsx` at line 7, Add a TypeScript type alias for the file object shape (e.g., FileItem or NewsFile) describing fields used by the component (id, name, url, mimeType, size, etc.), then annotate the DocumentsPreview component props as ({ files }: { files: FileItem[] }) or use an interface/type for props; update any internal variable typings (e.g., map callbacks) to use the new FileItem type so the files prop is no longer implicit any and IDE/type-checking work as expected.
9-15: Consider extracting inline styles to a CSS module.The inline styles for border, padding, and border-radius would be more maintainable in a CSS module file, consistent with patterns in other components like
InputStepForm.module.css.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/condo/domains/news/components/FilesPreview/DocumentsPreview.tsx` around lines 9 - 15, Extract the inline style objects from DocumentsPreview.tsx into a CSS module (e.g., DocumentsPreview.module.css) and replace the inline props with className references: move the container styles (width: '100%') currently on Row and Col into a .container or .fullWidth class, and move the card styles (borderRadius: 12, border: '1px solid `#E6E8F1`', padding: 12, width: '100%') from the inner div into a .fileCard class; then import the module into DocumentsPreview.tsx and apply these classes to the Row, Col and the div inside files?.map to keep the rendering logic (files?.map, key using file?.id || file?.uid || ind, and Typography.Text) unchanged.apps/condo/domains/news/components/FilesPreview/ImageOrVideoPreview.tsx (1)
85-85: Add TypeScript types for thefileprop.The
fileparameter lacks type annotation, resulting in implicitany. This reduces type safety and makes the expected file structure unclear to consumers.♻️ Proposed fix to add types
+type FileItem = { + id?: string + uid?: string + url?: string + response?: { + url?: string + mimetype?: string + } +} + +type ImageOrVideoPreviewProps = { + file: FileItem +} + -export const ImageOrVideoPreview = ({ file }) => { +export const ImageOrVideoPreview: React.FC<ImageOrVideoPreviewProps> = ({ file }) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/condo/domains/news/components/FilesPreview/ImageOrVideoPreview.tsx` at line 85, The ImageOrVideoPreview component's parameter lacks a TypeScript type; add an explicit prop type for file (either create a local interface like FilePreviewProps { file: { url: string; mimetype?: string; ext?: string; name?: string; id?: string; /* add any other fields used in the component */ } } or import an existing domain/media type) and annotate the component signature (e.g., export const ImageOrVideoPreview: React.FC<FilePreviewProps> = ({ file }) => { ... }) so consumers and the compiler know the expected file shape and optional fields used by ImageOrVideoPreview.apps/condo/domains/news/components/FilesUploadList.module.css (2)
61-70: Consider consolidating duplicate duration styles.
.duration(lines 61-70) and.video-duration(lines 125-134) have identical styling. Consider using a single class or extracting shared styles.Suggested consolidation
/* Duration badge - used for both images and videos */ .duration { position: absolute; right: 4px; bottom: 4px; padding: 2px 6px; color: `#fff`; font-size: 10px; background: rgb(0 0 0 / 70%); border-radius: 4px; } - -.video-duration { - position: absolute; - right: 4px; - bottom: 4px; - padding: 2px 6px; - color: white; - font-size: 10px; - background: rgb(0 0 0 / 70%); - border-radius: 4px; -}Then use
.durationclass for both image and video duration badges in the component.Also applies to: 125-134
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/condo/domains/news/components/FilesUploadList.module.css` around lines 61 - 70, The .duration and .video-duration blocks are duplicate; remove the redundancy by keeping a single shared CSS class (e.g., .duration) with the background, positioning, padding, font-size, color and border-radius, delete or collapse .video-duration, and update the component to apply .duration for both image and video duration badges (replace references to video-duration with duration) so both use the same style rule; ensure no other unique rules from .video-duration are lost when consolidating.
24-24: Translate Russian comments to English.The CSS module contains Russian comments that should be in English for consistency with the codebase:
- Line 24:
/* Контент */→/* Content */- Line 49:
/* Название */→/* File name */- Line 60:
/* Длительность */→/* Duration */- Line 72:
/* Overlay */→ (already English)- Line 85:
/* Кнопки */→/* Buttons */- Line 109:
/* Hover */→ (already English)Also applies to: 49-49, 60-60, 72-72, 85-85, 109-109
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/condo/domains/news/components/FilesUploadList.module.css` at line 24, In FilesUploadList.module.css replace the Russian comment tokens with English equivalents for consistency: change "/* Контент */" to "/* Content */", "/* Название */" to "/* File name */", "/* Длительность */" to "/* Duration */", and "/* Кнопки */" to "/* Buttons */" (leave existing English comments "/* Overlay */" and "/* Hover */" unchanged); update those exact comment strings in the file so selectors and spacing remain untouched.apps/condo/domains/news/components/NewsForm/UpdateNewsForm.tsx (1)
19-21: Import organization: consider grouping relative imports.The blank line between the
BaseNewsFormimport (line 18) and thegqlimports (lines 20-21) creates an unintended separation. These relative imports from parent directories should be grouped together per the import ordering guidelines.Suggested fix
import { SendPeriodType, BaseNewsFormProps, BaseNewsForm } from './BaseNewsForm' - -import { useGetNewsItemFilesQuery } from '../../../../gql' -import { convertFilesToUploadType } from '../FilesUploadList' +import { useGetNewsItemFilesQuery } from '../../../../gql' +import { convertFilesToUploadType } from '../FilesUploadList' + export interface IUpdateNewsForm {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/condo/domains/news/components/NewsForm/UpdateNewsForm.tsx` around lines 19 - 21, The import grouping is split by an extra blank line; move the relative imports useGetNewsItemFilesQuery and convertFilesToUploadType so they sit with the BaseNewsForm import (group all relative/parent-directory imports together) in UpdateNewsForm.tsx, remove the blank line, and ensure import order follows external libs → absolute project imports → grouped relative imports (e.g., BaseNewsForm, useGetNewsItemFilesQuery, convertFilesToUploadType).apps/condo/domains/news/components/NewsForm/InputStep/InputStepFilesSelector.tsx (1)
1-15: Reorder imports to match repository grouping rules
@app/condo/*imports are currently placed before external and@open-condo/*imports. Please reorder by group and keep case-insensitive alphabetical order within each group.As per coding guidelines, "Enforce import order groups: builtin → external →
@open-condo→ internal → sibling → parent with newlines between groups" and "Alphabetize imports case-insensitively within each import group".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/condo/domains/news/components/NewsForm/InputStep/InputStepFilesSelector.tsx` around lines 1 - 15, Reorder the import block in InputStepFilesSelector.tsx so groups follow the repo rule: builtin → external → `@open-condo` → internal (e.g. `@app/condo`) → sibling/parent, with a blank line between groups; specifically move the `@app/condo` imports (useCreateNewsItemFileMutation, useGetNewsItemFilesLazyQuery, CreateNewsItemFileMutation) into the internal group after the `@open-condo` group, ensure `@open-condo` imports (buildMeta, uploadFiles, getClientSideSenderInfo, useAuth, useOrganization) come after external libs (react, antd, next/config, rc-upload, etc.), and alphabetize imports case-insensitively within each group (also order Action, DBFile, FilesUploadList among sibling imports accordingly).apps/condo/domains/news/components/FilesUploadList.tsx (1)
752-752: UseTypographyfor the file-type badge.This text is rendered with a raw
<span>. The repo standard here is to use@open-condo/uitypography components for text rendering.As per coding guidelines "Use Typography components from
@open-condo/uifor all text rendering instead of custom text elements."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/condo/domains/news/components/FilesUploadList.tsx` at line 752, Replace the raw <span> used to render file type in FilesUploadList.tsx with the Typography component from `@open-condo/ui`: locate the conditional that returns <span style={{ fontWeight: 'bold', color: 'grey' }}>{typeToView}</span> (the typeToView render path) and return a <Typography> (or Text/Bold variant per project conventions) with equivalent styling (bold and grey) instead; also add the import for the chosen Typography component from '@open-condo/ui' if missing. Ensure the visual style (fontWeight and color) is preserved via Typography props or className.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/condo/domains/news/components/FilesPreview/ImageOrVideoPreview.tsx`:
- Around line 10-13: The code assumes canvas.getContext('2d') always returns a
CanvasRenderingContext2D (in img.onload and in createVideoPreviewFromUrl) which
can be null; update the img.onload handler and createVideoPreviewFromUrl to
check the result of const ctx = canvas.getContext('2d') and handle the null case
(e.g., abort/return a rejected Promise or throw a descriptive error) before
using ctx, and ensure any downstream code that uses ctx is guarded so no methods
are called on a possibly null value.
- Around line 88-104: The async process() inside useDeepCompareEffect does not
catch errors from getImagePreviewFromUrl or createVideoPreviewFromUrl; wrap the
body of process() in try/catch (or attach .catch) to handle rejections, log or
otherwise handle the error, and ensure setPreview('') or a fallback is used on
failure; update the process function referenced in useDeepCompareEffect (which
calls getImagePreviewFromUrl/createVideoPreviewFromUrl and setPreview) to
prevent unhandled promise rejections.
- Around line 106-108: The <img> in ImageOrVideoPreview is missing an alt
attribute; update the JSX in the ImageOrVideoPreview component to include a
meaningful alt value (e.g., use a prop or variable like fileName, file.name, or
other available title) and fall back to an empty string if none is available
(alt={fileName ?? ''} or alt={file?.name || 'Preview image'}) so screen readers
and users with images disabled receive appropriate text.
In `@apps/condo/domains/news/components/FilesUploadList.tsx`:
- Around line 370-394: The file picker currently doesn't include 'image/webp' in
its accepted MIME types so the WebP→JPEG path in convertFile (the block handling
file.type === 'image/webp') is never used; update the file input's accept list
and any acceptedMimeTypes/ACCEPTED_MIME_TYPES array used by FilesUploadList (and
the other occurrence referenced at line ~648) to include 'image/webp' so the
picker allows WebP files and the convertFile branch becomes reachable.
- Around line 21-35: The module-level ffmpeg and ffmpegLoaded cause shared state
and mixed progress across parallel uploads; change loadFFmpeg to create and
return a fresh FFmpeg instance per upload (e.g., newFFmpeg or
createFFmpegInstance) instead of reusing the module-scoped ffmpeg/ffmpegLoaded,
attach per-upload 'log' and 'progress' listeners inside the upload flow (not
globally), and ensure cleanup/removeListener only removes the handlers bound to
that instance/closure so one upload cannot remove another upload's listener
(update places that call loadFFmpeg and the progress removal logic to use the
new per-upload instance and per-upload handler references).
- Around line 529-530: The UI message FileTooBigErrorMessage uses
MAX_UPLOAD_FILE_SIZE but the file-size validation still hardcodes 512 MB; update
the validation in FilesUploadList.tsx to compare file.size against
MAX_UPLOAD_FILE_SIZE (instead of 512 * 1024 * 1024) and replace any other
hardcoded 512MB checks (notably the other guard near the later upload handling
around the second occurrence) so both the error message and enforcement derive
from the same MAX_UPLOAD_FILE_SIZE constant.
- Around line 709-734: createAction persists the file but any error thrown while
generating preview (getImageThumbnailFromUrl or createVideoThumbnailFromUrl)
bubbles to the outer catch and marks the whole upload as failed; change the flow
so thumbnail generation is wrapped in its own try/catch: call createAction
first, then attempt thumbnail generation in a separate try block that logs the
thumbnail error (console.error) and falls back to an empty thumbnail, and always
call updateFileList and onSuccess after createAction; only call onError if
createAction itself fails. Ensure you reference createAction,
getImageThumbnailFromUrl, createVideoThumbnailFromUrl, updateFileList, onSuccess
and onError when applying the change.
- Around line 694-699: When convertFile(...) fails in FilesUploadList.tsx, don't
just console.error and rethrow — update the upload state so the item isn't left
stuck in "uploading". In the catch block for convertFile(file, onProgress) call
the component's upload-failure handler (e.g., markFileUploadFailed /
setFileStatus(file.id, 'failed') or invoke the existing onError/onUploadFail
callback) and clear any uploading flags or progress for that file, then
return/exit the flow instead of rethrowing so the UI reflects the failed state.
In `@apps/condo/domains/news/components/NewsForm/InputStep/index.tsx`:
- Around line 316-335: The effect reuploading files currently dispatches every
reuploaded NewsItemFile as modifyFiles({ type: 'add', ... }) which causes
duplicates because submit only deletes items listed in modifiedFiles.deleted;
instead initialize/sync the current files without marking them as "added": after
converting (convertFilesToUploadType) call setFiles and form.setFieldsValue({
'files': setFiles }) then call modifyFiles with a single operation that
replaces/initializes the file state (e.g., modifyFiles({ type: 'set' | 'replace'
| 'init', payload: converted }) or a dedicated sync method) rather than per-file
'add', so existing DB-backed attachments are not treated as newly added.
- Around line 328-329: The code is storing the setter function into the form
instead of the actual file list; after calling setFiles(converted) replace
form.setFieldsValue({ 'files': setFiles }) with form.setFieldsValue({ 'files':
converted }) so the form's files field contains the array of files (converted)
rather than the setFiles function; update the call around the
setFiles(converted) line in InputStep/index.tsx accordingly.
In
`@apps/condo/domains/news/components/NewsForm/InputStep/InputStepFilesSelector.tsx`:
- Around line 198-201: Replace the hardcoded Russian helper texts in the
InputStepFilesSelector component (the Typography.Text nodes) with localized
messages via intl.formatMessage and correct the typo “EXEL” to “EXCEL”; also
replace the placeholder “? МБ” with the final video size limit (use 10 МБ if
that's the project standard) and add appropriate message IDs (e.g.
news.input.files.helperImageVideo and news.input.files.helperDocument) so both
lines call intl.formatMessage({ id: '...' }) and include the finalized limits
and corrected spelling.
- Around line 90-109: The code path that calls getNewsItemFiles and
reuploadNewsItemFile can throw and never reaches setIsLoading(false), leaving
the component stuck loading; wrap the async logic inside a try/catch/finally
around the block that calls getNewsItemFiles, maps files, and awaits
reuploadNewsItemFile so you can handle errors and always call
setIsLoading(false) in the finally block, logging or handling errors in catch
and still calling setReuploadedNewsItemFiles(result) only on success (or set an
empty result on failure) to ensure consistent state.
- Around line 158-167: The fallback branch in InputStepFilesSelector assigns the
raw rc-upload file object to createInput which is not the uploaded-file payload
expected by the createNewsItemFile mutation; update the fallback to build the
same shape used in the successful-upload branch (include signature,
originalFilename, mimetype and any other fields the GraphQL input expects) by
extracting values from the rc-upload object (e.g., originFileObj, name, type) or
by reusing the upload response transform used elsewhere before calling
createNewsItemFile with that properly shaped createInput.
- Around line 36-40: The state and callback types mismatch: change
reuploadedNewsItemFiles to allow null by typing useState as
Array<CreateNewsItemFileMutation['newsItemFile']> | null and keep the initial
value null; update reuploadNewsItemFile's signature to return
Promise<CreateNewsItemFileMutation['newsItemFile'] | null> (or
Promise<NonNullable<...> | null>) and replace the bare early `return` with
`return null`, so callers and the later .filter(Boolean) usage align with the
nullable contract; adjust any related setReuploadedNewsItemFiles usage if
needed.
In `@apps/condo/domains/news/components/NewsPreview.tsx`:
- Around line 222-238: CondoAppPreview's filesByGroups useMemo assumes files is
always an array and calls files.reduce(...), which will throw when files is
undefined; update the useMemo to guard/normalize files first (e.g., treat files
= files || [] before computing) so isImageOrVideo and the reduce operate on a
safe array; refer to the filesByGroups constant, the useMemo callback and the
isImageOrVideo helper when making the change.
In `@apps/condo/domains/news/queries/NewsItemFile.graphql`:
- Around line 15-23: The GraphQL query getNewsItemFiles currently sets a default
cap ($first: Int = 10) which hides attachments beyond the 10th; remove the
default limit by changing the variable signature to use $first: Int (i.e.,
delete "= 10") so the query does not silently cap results, and then update
callers that need the full set (detail and reupload flows) to pass an explicit
high/appropriate first value or omit first if your GraphQL server returns all
when absent.
In `@apps/condo/gql/index.ts`:
- Around line 3355-3399: The generated query getNewsItemFiles
(GetNewsItemFilesDocument) defaults first: 10 with no skip, causing truncation;
update the source GraphQL (apps/condo/domains/news/queries/NewsItemFile.graphql)
to add a skip: Int = 0 parameter and include skip in the allNewsItemFiles call,
then regenerate the codegen so the exported hooks (useGetNewsItemFilesQuery,
useGetNewsItemFilesLazyQuery, useGetNewsItemFilesSuspenseQuery) accept and
forward skip via Types.GetNewsItemFilesQueryVariables to avoid silently limiting
results.
In `@apps/condo/lang/es/es.json`:
- Line 1594: The label value for the key "news.fields.files.label" is
inconsistent with other News action verbs; update its string from "Agregar
archivos multimedia" to use the same verb as the rest of the locale (e.g.,
"Añadir archivos multimedia") by editing the value for news.fields.files.label
so it matches the existing "Añadir" phrasing used across the News labels.
In `@apps/condo/migrations/20260413232745-0524_alter_newsitemfile_newsitem.js`:
- Around line 19-30: The down migration (exports.down) blindly runs ALTER TABLE
"NewsItemFile" ALTER COLUMN "newsItem" SET NOT NULL which will fail if any
NewsItemFile rows have newsItem = NULL; before applying the NOT NULL change, add
a safe step to handle or remove orphaned rows — e.g., delete or update rows
where "newsItem" IS NULL (or fail with a clear message), ensuring the migration
uses the "NewsItemFile" table and "newsItem" column and preserves/adjusts the
existing constraint "NewsItemFile_newsItem_7d5eb5a1_fk_NewsItem_id" accordingly
so the ALTER COLUMN can succeed.
In `@apps/condo/pages/news/`[id]/index.tsx:
- Around line 128-140: The attachment query currently only destructures loading
and data from useGetNewsItemFilesQuery (assigned to
newsItemFilesLoading/newsItemFilesData) and the UI's hasError check doesn't
include query errors, so failures render as "no files"; update the hook
destructuring to also get error (e.g., newsItemFilesError) and incorporate that
into the component's error state/hasError logic, then render an explicit error
UI/message for attachments when newsItemFilesError is truthy instead of the
empty media section; apply the same change to the other similar query block
around the second occurrence (the block at ~lines 308-313) so both attachment
queries expose and handle their error values.
---
Outside diff comments:
In `@apps/condo/domains/news/access/NewsItemFile.js`:
- Around line 70-81: Add a scheduled cleanup job that soft-deletes orphaned
NewsItemFile rows (where newsItem is null) older than a configurable TTL (e.g.,
NEWS_ITEM_FILE_TTL); create a task module (e.g.,
cleanupOrphanedNewsItemFiles.js) that queries the NewsItemFile model for records
with newsItem == null and createdAt < now - TTL, then soft-delete them using the
same soft-delete method/pattern your app uses (e.g., set deletedAt or call the
existing softDelete helper) and wire the task into the existing scheduler
similar to notifyResidentsAboutNewsItem.js; expose TTL via config/env and ensure
the task logs what it deletes and is idempotent so it can run regularly.
In `@apps/condo/domains/news/components/NewsForm/BaseNewsForm.tsx`:
- Around line 628-878: The useCallback for handleFormSubmit is missing several
dependencies which causes stale closures; update the dependency array of
handleFormSubmit to include selectAppsFormValues, condoFormValues,
sharingAppsFormValues, modifiedFiles, updateNewsItemFile, client,
getSelectedAndNotSkippedSharingApps, selectedProperties (in addition to the
existing ones like actionName, createOrUpdateNewsItem, updateNewsItem, etc.) so
the callback sees current form values, file lists, sharing configs and property
data when it runs.
In `@apps/condo/domains/news/schema/NewsItemFile.js`:
- Around line 99-106: The code calls mimetype.replaceAll without guarding
mimetype, causing a TypeError when FileRecord or fileMimeType is missing; before
splitting, check that fileRecord?.fileMimeType (the mimetype variable) is
defined/truthy and if not throw the existing GQLError using
ERRORS.UNKNOWN_FILE_TYPE with context; then compute normalizedMimetype (e.g.,
after guarding, call replaceAll and split) and proceed—update the block around
getByCondition('FileRecord'), mimetype, normalizedMimetype, and the GQLError
throw to perform the presence check before calling replaceAll.
---
Nitpick comments:
In `@apps/condo/domains/news/components/FilesPreview/DocumentsPreview.tsx`:
- Line 7: Add a TypeScript type alias for the file object shape (e.g., FileItem
or NewsFile) describing fields used by the component (id, name, url, mimeType,
size, etc.), then annotate the DocumentsPreview component props as ({ files }: {
files: FileItem[] }) or use an interface/type for props; update any internal
variable typings (e.g., map callbacks) to use the new FileItem type so the files
prop is no longer implicit any and IDE/type-checking work as expected.
- Around line 9-15: Extract the inline style objects from DocumentsPreview.tsx
into a CSS module (e.g., DocumentsPreview.module.css) and replace the inline
props with className references: move the container styles (width: '100%')
currently on Row and Col into a .container or .fullWidth class, and move the
card styles (borderRadius: 12, border: '1px solid `#E6E8F1`', padding: 12, width:
'100%') from the inner div into a .fileCard class; then import the module into
DocumentsPreview.tsx and apply these classes to the Row, Col and the div inside
files?.map to keep the rendering logic (files?.map, key using file?.id ||
file?.uid || ind, and Typography.Text) unchanged.
In `@apps/condo/domains/news/components/FilesPreview/ImageOrVideoPreview.tsx`:
- Line 85: The ImageOrVideoPreview component's parameter lacks a TypeScript
type; add an explicit prop type for file (either create a local interface like
FilePreviewProps { file: { url: string; mimetype?: string; ext?: string; name?:
string; id?: string; /* add any other fields used in the component */ } } or
import an existing domain/media type) and annotate the component signature
(e.g., export const ImageOrVideoPreview: React.FC<FilePreviewProps> = ({ file })
=> { ... }) so consumers and the compiler know the expected file shape and
optional fields used by ImageOrVideoPreview.
In `@apps/condo/domains/news/components/FilesUploadList.module.css`:
- Around line 61-70: The .duration and .video-duration blocks are duplicate;
remove the redundancy by keeping a single shared CSS class (e.g., .duration)
with the background, positioning, padding, font-size, color and border-radius,
delete or collapse .video-duration, and update the component to apply .duration
for both image and video duration badges (replace references to video-duration
with duration) so both use the same style rule; ensure no other unique rules
from .video-duration are lost when consolidating.
- Line 24: In FilesUploadList.module.css replace the Russian comment tokens with
English equivalents for consistency: change "/* Контент */" to "/* Content */",
"/* Название */" to "/* File name */", "/* Длительность */" to "/* Duration */",
and "/* Кнопки */" to "/* Buttons */" (leave existing English comments "/*
Overlay */" and "/* Hover */" unchanged); update those exact comment strings in
the file so selectors and spacing remain untouched.
In `@apps/condo/domains/news/components/FilesUploadList.tsx`:
- Line 752: Replace the raw <span> used to render file type in
FilesUploadList.tsx with the Typography component from `@open-condo/ui`: locate
the conditional that returns <span style={{ fontWeight: 'bold', color: 'grey'
}}>{typeToView}</span> (the typeToView render path) and return a <Typography>
(or Text/Bold variant per project conventions) with equivalent styling (bold and
grey) instead; also add the import for the chosen Typography component from
'@open-condo/ui' if missing. Ensure the visual style (fontWeight and color) is
preserved via Typography props or className.
In
`@apps/condo/domains/news/components/NewsForm/InputStep/InputStepFilesSelector.tsx`:
- Around line 1-15: Reorder the import block in InputStepFilesSelector.tsx so
groups follow the repo rule: builtin → external → `@open-condo` → internal (e.g.
`@app/condo`) → sibling/parent, with a blank line between groups; specifically
move the `@app/condo` imports (useCreateNewsItemFileMutation,
useGetNewsItemFilesLazyQuery, CreateNewsItemFileMutation) into the internal
group after the `@open-condo` group, ensure `@open-condo` imports (buildMeta,
uploadFiles, getClientSideSenderInfo, useAuth, useOrganization) come after
external libs (react, antd, next/config, rc-upload, etc.), and alphabetize
imports case-insensitively within each group (also order Action, DBFile,
FilesUploadList among sibling imports accordingly).
In `@apps/condo/domains/news/components/NewsForm/UpdateNewsForm.tsx`:
- Around line 19-21: The import grouping is split by an extra blank line; move
the relative imports useGetNewsItemFilesQuery and convertFilesToUploadType so
they sit with the BaseNewsForm import (group all relative/parent-directory
imports together) in UpdateNewsForm.tsx, remove the blank line, and ensure
import order follows external libs → absolute project imports → grouped relative
imports (e.g., BaseNewsForm, useGetNewsItemFilesQuery,
convertFilesToUploadType).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0e8c6725-5b3c-452a-b668-21323c59f7d5
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (27)
apps/condo/domains/common/utils/next/apollo.tsapps/condo/domains/news/access/NewsItemFile.jsapps/condo/domains/news/components/FilesPreview/DocumentsPreview.tsxapps/condo/domains/news/components/FilesPreview/ImageOrVideoPreview.tsxapps/condo/domains/news/components/FilesUploadList.module.cssapps/condo/domains/news/components/FilesUploadList.tsxapps/condo/domains/news/components/NewsForm/BaseNewsForm.tsxapps/condo/domains/news/components/NewsForm/InputStep/InputStepFilesSelector.tsxapps/condo/domains/news/components/NewsForm/InputStep/InputStepForm.tsxapps/condo/domains/news/components/NewsForm/InputStep/InputStepPreview.tsxapps/condo/domains/news/components/NewsForm/InputStep/InputStepRecipientCounter.tsxapps/condo/domains/news/components/NewsForm/InputStep/InputStepSelector.tsxapps/condo/domains/news/components/NewsForm/InputStep/index.tsxapps/condo/domains/news/components/NewsForm/UpdateNewsForm.tsxapps/condo/domains/news/components/NewsPreview.tsxapps/condo/domains/news/queries/NewsItemFile.graphqlapps/condo/domains/news/schema/NewsItemFile.jsapps/condo/domains/news/schema/NewsItemFile.test.jsapps/condo/domains/news/utils/testSchema/index.jsapps/condo/gql/index.tsapps/condo/gql/operation.types.tsapps/condo/lang/en/en.jsonapps/condo/lang/es/es.jsonapps/condo/lang/ru/ru.jsonapps/condo/migrations/20260413232745-0524_alter_newsitemfile_newsitem.jsapps/condo/package.jsonapps/condo/pages/news/[id]/index.tsx
| img.onload = () => { | ||
| const canvas = document.createElement('canvas') | ||
| const ctx = canvas.getContext('2d') | ||
|
|
There was a problem hiding this comment.
Handle potential null canvas context.
getContext('2d') can return null if the context identifier is not supported. Using ctx without a null check can cause runtime errors.
🛡️ Proposed fix to add null checks
img.onload = () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
+ if (!ctx) {
+ reject(new Error('Canvas 2D context not supported'))
+ return
+ }
const WIDTH = 1280Apply similar fix in createVideoPreviewFromUrl:
const video = document.createElement('video')
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
+if (!ctx) {
+ reject(new Error('Canvas 2D context not supported'))
+ return
+}Also applies to: 43-46
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/condo/domains/news/components/FilesPreview/ImageOrVideoPreview.tsx`
around lines 10 - 13, The code assumes canvas.getContext('2d') always returns a
CanvasRenderingContext2D (in img.onload and in createVideoPreviewFromUrl) which
can be null; update the img.onload handler and createVideoPreviewFromUrl to
check the result of const ctx = canvas.getContext('2d') and handle the null case
(e.g., abort/return a rejected Promise or throw a descriptive error) before
using ctx, and ensure any downstream code that uses ctx is guarded so no methods
are called on a possibly null value.
| useDeepCompareEffect(() => { | ||
| const process = async () => { | ||
| const url = file?.response?.url || file?.url | ||
|
|
||
| let thumb = '' | ||
| if (file.response?.mimetype?.startsWith('image/')) { | ||
| thumb = await getImagePreviewFromUrl(url) | ||
| } | ||
| if (file.response?.mimetype?.startsWith('video/')) { | ||
| thumb = await createVideoPreviewFromUrl(url) | ||
| } | ||
|
|
||
| setPreview(thumb) | ||
| } | ||
|
|
||
| process() | ||
| }, [file]) |
There was a problem hiding this comment.
Handle errors from async thumbnail generation.
The process() function is async but errors are not caught. If getImagePreviewFromUrl or createVideoPreviewFromUrl rejects, it will cause an unhandled promise rejection.
🛡️ Proposed fix to add error handling
useDeepCompareEffect(() => {
const process = async () => {
const url = file?.response?.url || file?.url
+ if (!url) return
let thumb = ''
- if (file.response?.mimetype?.startsWith('image/')) {
- thumb = await getImagePreviewFromUrl(url)
- }
- if (file.response?.mimetype?.startsWith('video/')) {
- thumb = await createVideoPreviewFromUrl(url)
+ try {
+ if (file.response?.mimetype?.startsWith('image/')) {
+ thumb = await getImagePreviewFromUrl(url)
+ } else if (file.response?.mimetype?.startsWith('video/')) {
+ thumb = await createVideoPreviewFromUrl(url)
+ }
+ } catch (error) {
+ console.error('Failed to generate preview:', error)
+ return
}
setPreview(thumb)
}
process()
}, [file])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/condo/domains/news/components/FilesPreview/ImageOrVideoPreview.tsx`
around lines 88 - 104, The async process() inside useDeepCompareEffect does not
catch errors from getImagePreviewFromUrl or createVideoPreviewFromUrl; wrap the
body of process() in try/catch (or attach .catch) to handle rejections, log or
otherwise handle the error, and ensure setPreview('') or a fallback is used on
failure; update the process function referenced in useDeepCompareEffect (which
calls getImagePreviewFromUrl/createVideoPreviewFromUrl and setPreview) to
prevent unhandled promise rejections.
| return ( | ||
| <img src={preview} style={{ borderRadius: 12, border: '1px solid #E6E8F1', width: '100%' }} /> | ||
| ) |
There was a problem hiding this comment.
Add alt attribute for accessibility.
The <img> element is missing an alt attribute, which is required for accessibility. Screen readers and users with images disabled need this information.
🛡️ Proposed fix
- <img src={preview} style={{ borderRadius: 12, border: '1px solid `#E6E8F1`', width: '100%' }} />
+ <img src={preview} alt="" style={{ borderRadius: 12, border: '1px solid `#E6E8F1`', width: '100%' }} />Consider passing a meaningful alt text if the file name is available:
- <img src={preview} style={{ borderRadius: 12, border: '1px solid `#E6E8F1`', width: '100%' }} />
+ <img
+ src={preview}
+ alt={file?.response?.originalName || 'Preview'}
+ style={{ borderRadius: 12, border: '1px solid `#E6E8F1`', width: '100%' }}
+ />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return ( | |
| <img src={preview} style={{ borderRadius: 12, border: '1px solid #E6E8F1', width: '100%' }} /> | |
| ) | |
| return ( | |
| <img src={preview} alt="" style={{ borderRadius: 12, border: '1px solid `#E6E8F1`', width: '100%' }} /> | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/condo/domains/news/components/FilesPreview/ImageOrVideoPreview.tsx`
around lines 106 - 108, The <img> in ImageOrVideoPreview is missing an alt
attribute; update the JSX in the ImageOrVideoPreview component to include a
meaningful alt value (e.g., use a prop or variable like fileName, file.name, or
other available title) and fall back to an empty string if none is available
(alt={fileName ?? ''} or alt={file?.name || 'Preview image'}) so screen readers
and users with images disabled receive appropriate text.
| let ffmpeg: FFmpeg | null = null | ||
| let ffmpegLoaded = false | ||
|
|
||
| const loadFFmpeg = async () => { | ||
| if (!ffmpeg) { | ||
| ffmpeg = new FFmpeg() | ||
| ffmpeg.on('log', ({ message }) => { | ||
| console.log(message) | ||
| }) | ||
| } | ||
| if (!ffmpegLoaded) { | ||
| await ffmpeg.load() | ||
| ffmpegLoaded = true | ||
| } | ||
| } |
There was a problem hiding this comment.
Isolate FFmpeg state per upload.
multiple allows parallel video uploads, but every request shares the same module-level ffmpeg instance and the same progress listener set. Two concurrent conversions will report mixed progress, and one request can remove the other request’s handler on Line 424.
Also applies to: 397-425, 647-647
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/condo/domains/news/components/FilesUploadList.tsx` around lines 21 - 35,
The module-level ffmpeg and ffmpegLoaded cause shared state and mixed progress
across parallel uploads; change loadFFmpeg to create and return a fresh FFmpeg
instance per upload (e.g., newFFmpeg or createFFmpegInstance) instead of reusing
the module-scoped ffmpeg/ffmpegLoaded, attach per-upload 'log' and 'progress'
listeners inside the upload flow (not globally), and ensure
cleanup/removeListener only removes the handlers bound to that instance/closure
so one upload cannot remove another upload's listener (update places that call
loadFFmpeg and the progress removal logic to use the new per-upload instance and
per-upload handler references).
| // 🖼 WebP → JPEG | ||
| if (file.type === 'image/webp') { | ||
| const img = await new Promise<HTMLImageElement>((resolve, reject) => { | ||
| const i = new Image() | ||
| i.onload = () => resolve(i) | ||
| i.onerror = reject | ||
| i.src = URL.createObjectURL(file) | ||
| }) | ||
|
|
||
| const canvas = document.createElement('canvas') | ||
| canvas.width = img.width | ||
| canvas.height = img.height | ||
| const ctx = canvas.getContext('2d') | ||
| ctx.drawImage(img, 0, 0) | ||
|
|
||
| const blob = await new Promise<Blob | null>((resolve) => { | ||
| canvas.toBlob(resolve, 'image/jpeg', 0.9) | ||
| }) | ||
|
|
||
| return new File( | ||
| [blob], | ||
| file.name.replace(/\.webp$/i, '.jpeg'), | ||
| { type: 'image/jpeg' } | ||
| ) | ||
| } |
There was a problem hiding this comment.
Add WebP to the accepted mime list.
convertFile already has a WebP → JPEG path, but the picker does not accept image/webp, so that branch is unreachable from normal file selection.
Also applies to: 648-648
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/condo/domains/news/components/FilesUploadList.tsx` around lines 370 -
394, The file picker currently doesn't include 'image/webp' in its accepted MIME
types so the WebP→JPEG path in convertFile (the block handling file.type ===
'image/webp') is never used; update the file input's accept list and any
acceptedMimeTypes/ACCEPTED_MIME_TYPES array used by FilesUploadList (and the
other occurrence referenced at line ~648) to include 'image/webp' so the picker
allows WebP files and the convertFile branch becomes reachable.
| query getNewsItemFiles ($where: NewsItemFileWhereInput!, $sortBy: [SortNewsItemFilesBy!] = [createdAt_ASC], $first: Int = 10) { | ||
| newsItemFiles: allNewsItemFiles( | ||
| where: $where, | ||
| sortBy: $sortBy, | ||
| first: $first | ||
| ) { | ||
| id | ||
| file { publicUrl originalFilename mimetype } | ||
| } |
There was a problem hiding this comment.
Don’t cap attachment fetches at 10 by default.
This query is reused for the detail page and the reupload flow, and both paths expect the full attachment set. With first: 10 as the default, the 11th+ file silently disappears from view/edit unless every caller remembers to override it.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/condo/domains/news/queries/NewsItemFile.graphql` around lines 15 - 23,
The GraphQL query getNewsItemFiles currently sets a default cap ($first: Int =
10) which hides attachments beyond the 10th; remove the default limit by
changing the variable signature to use $first: Int (i.e., delete "= 10") so the
query does not silently cap results, and then update callers that need the full
set (detail and reupload flows) to pass an explicit high/appropriate first value
or omit first if your GraphQL server returns all when absent.
| query getNewsItemFiles($where: NewsItemFileWhereInput!, $sortBy: [SortNewsItemFilesBy!] = [createdAt_ASC], $first: Int = 10) { | ||
| newsItemFiles: allNewsItemFiles(where: $where, sortBy: $sortBy, first: $first) { | ||
| id | ||
| file { | ||
| publicUrl | ||
| originalFilename | ||
| mimetype | ||
| } | ||
| } | ||
| } | ||
| `; | ||
|
|
||
| /** | ||
| * __useGetNewsItemFilesQuery__ | ||
| * | ||
| * To run a query within a React component, call `useGetNewsItemFilesQuery` and pass it any options that fit your needs. | ||
| * When your component renders, `useGetNewsItemFilesQuery` returns an object from Apollo Client that contains loading, error, and data properties | ||
| * you can use to render your UI. | ||
| * | ||
| * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; | ||
| * | ||
| * @example | ||
| * const { data, loading, error } = useGetNewsItemFilesQuery({ | ||
| * variables: { | ||
| * where: // value for 'where' | ||
| * sortBy: // value for 'sortBy' | ||
| * first: // value for 'first' | ||
| * }, | ||
| * }); | ||
| */ | ||
| export function useGetNewsItemFilesQuery(baseOptions: Apollo.QueryHookOptions<Types.GetNewsItemFilesQuery, Types.GetNewsItemFilesQueryVariables> & ({ variables: Types.GetNewsItemFilesQueryVariables; skip?: boolean; } | { skip: boolean; }) ) { | ||
| const options = {...defaultOptions, ...baseOptions} | ||
| return Apollo.useQuery<Types.GetNewsItemFilesQuery, Types.GetNewsItemFilesQueryVariables>(GetNewsItemFilesDocument, options); | ||
| } | ||
| export function useGetNewsItemFilesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<Types.GetNewsItemFilesQuery, Types.GetNewsItemFilesQueryVariables>) { | ||
| const options = {...defaultOptions, ...baseOptions} | ||
| return Apollo.useLazyQuery<Types.GetNewsItemFilesQuery, Types.GetNewsItemFilesQueryVariables>(GetNewsItemFilesDocument, options); | ||
| } | ||
| // @ts-ignore | ||
| export function useGetNewsItemFilesSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions<Types.GetNewsItemFilesQuery, Types.GetNewsItemFilesQueryVariables>): Apollo.UseSuspenseQueryResult<Types.GetNewsItemFilesQuery, Types.GetNewsItemFilesQueryVariables>; | ||
| export function useGetNewsItemFilesSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<Types.GetNewsItemFilesQuery, Types.GetNewsItemFilesQueryVariables>): Apollo.UseSuspenseQueryResult<Types.GetNewsItemFilesQuery | undefined, Types.GetNewsItemFilesQueryVariables>; | ||
| export function useGetNewsItemFilesSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<Types.GetNewsItemFilesQuery, Types.GetNewsItemFilesQueryVariables>) { | ||
| const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions} | ||
| return Apollo.useSuspenseQuery<Types.GetNewsItemFilesQuery, Types.GetNewsItemFilesQueryVariables>(GetNewsItemFilesDocument, options); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Locate source query file and inspect current definition
fd -i 'NewsItemFile.graphql'
rg -n -C3 'query\s+getNewsItemFiles|allNewsItemFiles|first:\s*\$first|skip:\s*\$skip' apps/condo/domains/news
# 2) Check all hook callsites and whether they pass `first`
rg -n -C4 '\buseGetNewsItemFilesQuery\s*\(' --type=ts --type=tsx
# 3) Check for explicit attachment-count limits in news domain
rg -n -C3 'max.*files|files.*max|attachments?.*limit|MAX_.*FILES|limit.*attachments?' apps/condo/domains/news --type=ts --type=tsxRepository: open-condo-software/condo
Length of output: 2366
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find all callsites of useGetNewsItemFilesQuery (without file type restriction)
echo "=== Callsites of useGetNewsItemFilesQuery ==="
rg -n -C4 'useGetNewsItemFilesQuery\s*\(' apps/condo --type-list | head -20
rg 'useGetNewsItemFilesQuery' apps/condo -n -C5 2>/dev/null | head -100
# Check for any attachment count limits
echo -e "\n=== Attachment limits in news domain ==="
rg 'attach|file' apps/condo/domains/news -i -n --type-list 2>/dev/null | grep -i 'max\|limit' | head -20
rg -i '(max.*file|file.*max|attach.*limit|limit.*attach)' apps/condo/domains/news -n -C2 2>/dev/null | head -100Repository: open-condo-software/condo
Length of output: 521
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find all callsites of useGetNewsItemFilesQuery
echo "=== Callsites of useGetNewsItemFilesQuery ==="
rg 'useGetNewsItemFilesQuery' apps/condo -n -C6
# Search for file/attachment limits
echo -e "\n=== File attachment limits ==="
rg -i 'max.*attachment|attach.*max|file.*limit|limit.*file' apps/condo/domains/news -n -C2
# Check NewsItemFile.graphql for full definition
echo -e "\n=== Full NewsItemFile.graphql ==="
cat apps/condo/domains/news/queries/NewsItemFile.graphqlRepository: open-condo-software/condo
Length of output: 8663
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Get full context around the first callsite
echo "=== First callsite: news/[id]/index.tsx ==="
sed -n '131,145p' apps/condo/pages/news/[id]/index.tsx
# Get full context around the second callsite
echo -e "\n=== Second callsite: UpdateNewsForm.tsx ==="
sed -n '87,100p' apps/condo/domains/news/components/NewsForm/UpdateNewsForm.tsx
# Check if there's any upload limit or attachment validation in the form
echo -e "\n=== File upload validation/limits ==="
rg -i 'uploadfile|maxfilecount|maxfiles|filelist.*length|files.length' apps/condo/domains/news/components -n -C2 | head -50Repository: open-condo-software/condo
Length of output: 4867
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check for backend validation on NewsItemFile
echo "=== Backend validation for NewsItemFile ==="
find apps/condo/domains/news -name '*.ts' -o -name '*.js' | xargs grep -l 'NewsItemFile' | head -10
# Look for any limits in resolvers or hooks
echo -e "\n=== File creation/validation logic ==="
rg 'createNewsItemFile|allNewsItemFiles|MAX_.*FILE|.*\.length.*>.*10|validateFile' apps/condo/domains/news --type=ts -n -C3 | head -80
# Check if files.length is used anywhere that might indicate UI-side limit
echo -e "\n=== Files array checks in UI ==="
rg 'files\.length|fileList\.length' apps/condo/domains/news/components -n -C2 | head -50Repository: open-condo-software/condo
Length of output: 7613
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check NewsItemFile schema definition
echo "=== NewsItemFile schema ==="
cat apps/condo/domains/news/schema/NewsItemFile.js
# Check if there's a max file limit in constants
echo -e "\n=== Common constants for uploads ==="
cat apps/condo/domains/common/constants/uploads.ts 2>/dev/null || cat apps/condo/domains/common/constants/uploads.js 2>/dev/null || find apps/condo -name "*upload*" -path "*/constants/*" -type f -exec cat {} \;Repository: open-condo-software/condo
Length of output: 5401
Data loss risk: getNewsItemFiles defaults to 10 results with no pagination.
Line 3355 sets first: Int = 10 and the query has no skip parameter. Both callsites (news/[id]/index.tsx and UpdateNewsForm.tsx) omit the first argument, relying entirely on the default limit. Since the backend enforces no file count limit, news items with >10 attachments will silently truncate—displaying only the first 10 files when viewed or edited.
Add a skip parameter to the source query in apps/condo/domains/news/queries/NewsItemFile.graphql, then regenerate the codegen output:
Suggested fix
- query getNewsItemFiles ($where: NewsItemFileWhereInput!, $sortBy: [SortNewsItemFilesBy!] = [createdAt_ASC], $first: Int = 10) {
- newsItemFiles: allNewsItemFiles(
- where: $where,
- sortBy: $sortBy,
- first: $first
- ) {
+ query getNewsItemFiles (
+ $where: NewsItemFileWhereInput!,
+ $sortBy: [SortNewsItemFilesBy!] = [createdAt_ASC],
+ $first: Int = 100,
+ $skip: Int = 0
+ ) {
+ newsItemFiles: allNewsItemFiles(
+ where: $where,
+ sortBy: $sortBy,
+ first: $first,
+ skip: $skip
+ ) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/condo/gql/index.ts` around lines 3355 - 3399, The generated query
getNewsItemFiles (GetNewsItemFilesDocument) defaults first: 10 with no skip,
causing truncation; update the source GraphQL
(apps/condo/domains/news/queries/NewsItemFile.graphql) to add a skip: Int = 0
parameter and include skip in the allNewsItemFiles call, then regenerate the
codegen so the exported hooks (useGetNewsItemFilesQuery,
useGetNewsItemFilesLazyQuery, useGetNewsItemFilesSuspenseQuery) accept and
forward skip via Types.GetNewsItemFilesQueryVariables to avoid silently limiting
results.
| "news.component.RecipientCounter.willReceive.label": "recibirá \nlocales", | ||
| "news.create.title": "Nueva noticia", | ||
| "news.createNews": "Añadir noticia", | ||
| "news.fields.files.label": "Agregar archivos multimedia", |
There was a problem hiding this comment.
Use consistent Spanish action verb in News form labels.
Line 1594 uses “Agregar” while nearby News actions use “Añadir”. For consistency in this locale, align this label with existing wording.
Suggested change
- "news.fields.files.label": "Agregar archivos multimedia",
+ "news.fields.files.label": "Añadir archivos multimedia",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "news.fields.files.label": "Agregar archivos multimedia", | |
| "news.fields.files.label": "Añadir archivos multimedia", |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/condo/lang/es/es.json` at line 1594, The label value for the key
"news.fields.files.label" is inconsistent with other News action verbs; update
its string from "Agregar archivos multimedia" to use the same verb as the rest
of the locale (e.g., "Añadir archivos multimedia") by editing the value for
news.fields.files.label so it matches the existing "Añadir" phrasing used across
the News labels.
| exports.down = async (knex) => { | ||
| await knex.raw(` | ||
| BEGIN; | ||
| -- | ||
| -- Alter field newsItem on newsitemfile | ||
| -- | ||
| SET CONSTRAINTS "NewsItemFile_newsItem_7d5eb5a1_fk_NewsItem_id" IMMEDIATE; ALTER TABLE "NewsItemFile" DROP CONSTRAINT "NewsItemFile_newsItem_7d5eb5a1_fk_NewsItem_id"; | ||
| ALTER TABLE "NewsItemFile" ALTER COLUMN "newsItem" SET NOT NULL; | ||
| ALTER TABLE "NewsItemFile" ADD CONSTRAINT "NewsItemFile_newsItem_7d5eb5a1_fk_NewsItem_id" FOREIGN KEY ("newsItem") REFERENCES "NewsItem" ("id") DEFERRABLE INITIALLY DEFERRED; | ||
| COMMIT; | ||
|
|
||
| `) |
There was a problem hiding this comment.
Down migration will fail if NULL newsItem records exist.
The down migration's ALTER COLUMN "newsItem" SET NOT NULL will fail if any NewsItemFile records have NULL in the newsItem column. This is expected behavior, but consider adding a comment or ensuring orphaned files are cleaned up before running the down migration.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/condo/migrations/20260413232745-0524_alter_newsitemfile_newsitem.js`
around lines 19 - 30, The down migration (exports.down) blindly runs ALTER TABLE
"NewsItemFile" ALTER COLUMN "newsItem" SET NOT NULL which will fail if any
NewsItemFile rows have newsItem = NULL; before applying the NOT NULL change, add
a safe step to handle or remove orphaned rows — e.g., delete or update rows
where "newsItem" IS NULL (or fail with a clear message), ensuring the migration
uses the "NewsItemFile" table and "newsItem" column and preserves/adjusts the
existing constraint "NewsItemFile_newsItem_7d5eb5a1_fk_NewsItem_id" accordingly
so the ALTER COLUMN can succeed.
| const { | ||
| loading: newsItemFilesLoading, | ||
| data: newsItemFilesData, | ||
| } = useGetNewsItemFilesQuery({ | ||
| variables: { | ||
| where: { | ||
| newsItem: { | ||
| id: newsItemId, | ||
| }, | ||
| }, | ||
| }, | ||
| skip: !persistor || !newsItemId, | ||
| }) |
There was a problem hiding this comment.
Handle attachment-query failures instead of rendering an empty media section.
useGetNewsItemFilesQuery never exposes its error, and hasError does not account for it. If this request fails, the page renders as if the news item simply has no files, which hides real data loss from the user.
Also applies to: 308-313
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/condo/pages/news/`[id]/index.tsx around lines 128 - 140, The attachment
query currently only destructures loading and data from useGetNewsItemFilesQuery
(assigned to newsItemFilesLoading/newsItemFilesData) and the UI's hasError check
doesn't include query errors, so failures render as "no files"; update the hook
destructuring to also get error (e.g., newsItemFilesError) and incorporate that
into the component's error state/hasError logic, then render an explicit error
UI/message for attachments when newsItemFilesError is truthy instead of the
empty media section; apply the same change to the other similar query block
around the second occurrence (the block at ~lines 308-313) so both attachment
queries expose and handle their error values.


Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Documentation & Localization