Skip to content

refactor: consume file types, add doc string and merge image upload handling#839

Open
fingertips18 wants to merge 5 commits intomainfrom
refactor/file-integration
Open

refactor: consume file types, add doc string and merge image upload handling#839
fingertips18 wants to merge 5 commits intomainfrom
refactor/file-integration

Conversation

@fingertips18
Copy link
Owner

@fingertips18 fingertips18 commented Jan 20, 2026

Relates to #769

Summary by CodeRabbit

  • New Features

    • Added unified file upload endpoint (/file/upload).
    • Projects now support multiple preview files (previews).
  • Bug Fixes

    • Project cards render a preview image only when a valid preview URL exists.
  • Refactor

    • Migration from image-specific to generic file upload flow and types; image upload artifacts removed.
  • Documentation

    • API docs updated to reflect new file upload DTOs and endpoints.
  • Tests

    • Tests updated to reflect file-oriented upload flow and handlers.

✏️ Tip: You can customize this high-level summary in your review settings.

@fingertips18 fingertips18 self-assigned this Jan 20, 2026
@fingertips18 fingertips18 added the refactor Better alignment with project requirements and improved code clarity label Jan 20, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

Replaces image-specific upload/preview flow with a generic file upload flow: frontend renames Image→File types/services and Project.preview→previews: File[]; backend removes image DTOs/handlers/repos and adds FileUpload DTOs, FileHandler.Upload, FileRepository.Upload and a new /file/upload endpoint.

Changes

Cohort / File(s) Summary
Frontend API Routes
apps/admin/src/constants/api.ts
Renamed route property imagefile (${api}/image${api}/file).
Frontend Types
apps/admin/src/types/file.ts, apps/admin/src/types/index.ts, apps/admin/src/types/project.ts, apps/admin/src/types/image.ts
Added File/FileUpload types, mappers, validators and toJSON helpers; removed legacy types/image.ts; changed ensureString signature and added ensureDate/ensureNumber; Project: preview: stringpreviews: File[].
Frontend Services & UI
apps/admin/src/services/file.ts, apps/admin/src/pages/project/add/_components/form.tsx, apps/admin/src/pages/project/_components/card.tsx
Replaced ImageService→FileService and ImageService.upload→FileService.upload; project creation payload no longer includes preview; card now conditionally renders project.previews[0]?.url.
Backend API Docs & Specs
apps/backend/docs/..., apps/backend/docs/swagger.{json,yaml}, apps/backend/.mockery.yaml
Removed /image/upload and v1 image DTOs; added /file/upload and new FileUpload/FileUploaded DTOs; updated docs/specs and mockery config.
Backend Domain
apps/backend/internal/domain/file.go, removed .../domain/image.go
Introduced domain FileUpload, FileUploadRequest, FileUploaded, FileUploadedResponse and validation; removed legacy image domain types/validation.
Backend Handler (v1)
apps/backend/internal/handler/v1/file.go, apps/backend/internal/handler/v1/dto/file.go, removed .../image.go, apps/backend/internal/handler/v1/dto.go
Added FileHandler.Upload, DTOs for file upload, routing for /file/upload; removed image handler and image DTOs; updated mocks and tests to file variants.
Backend Repository (v1)
apps/backend/internal/repository/v1/file.go, apps/backend/internal/repository/v1/mocks/file_repository.go, removed .../image.go, removed .../mocks/image_repository.go
Added FileRepository.Upload and NewFileRepository with UploadthingSecretKey/httpAPI config; implemented HTTP POST to UploadThing, response parsing/validation; removed image repository and mocks.
Server Wiring & Tests
apps/backend/internal/server/server.go, apps/backend/internal/handler/v1/file_test.go, apps/backend/internal/repository/v1/file_test.go
Removed image route wiring; pass UploadthingSecretKey into file handler/repo; updated tests/fixtures to file-named handlers, repos, DTOs and /file/upload endpoint.

Sequence Diagram(s)

sequenceDiagram
    participant Admin as Admin UI
    participant FileSvc as Frontend FileService
    participant API as Backend /file/upload
    participant Repo as FileRepository
    participant UploadThing as UploadThing API

    Admin->>FileSvc: request upload (file metadata)
    FileSvc->>API: POST /file/upload { files, metadata }
    API->>Repo: map to domain.FileUploadRequest & validate
    Repo->>UploadThing: POST upload request (X-Uploadthing-Api-Key)
    UploadThing-->>Repo: 202 + uploaded file data
    Repo-->>API: validated domain.FileUploaded
    API-->>FileSvc: 202 + dto.FileUploadedDTO
    FileSvc-->>Admin: mapped file URL/fields
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I hopped from image to file with glee,

Previews now many, not just one for me,
FileService hums a tidy upload tune,
DTOs and handlers dance under the moon,
A crunchy carrot for the CI tonight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.96% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main refactoring effort: migrating from image-specific upload handling to a generalized file upload system, including documentation updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/admin/src/pages/project/_components/card.tsx (1)

19-24: Critical: Image source set to project ID instead of image URL.

Setting src={project.id} will result in a broken image since project.id is a UUID, not a URL. With the migration to previews: File[], you likely need to access the file URL from the first preview.

🐛 Proposed fix
         <img
-          src={project.id}
+          src={project.previews[0]?.url ?? ''}
           alt={`${project.title} preview`}
           sizes='(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw'
           onLoad={() => setImageLoaded(true)}
           className='absolute object-center object-cover size-full'
         />

You may also want to handle the case where previews is empty to avoid rendering a broken image or show a placeholder.

apps/admin/src/pages/project/add/_components/form.tsx (1)

122-176: Critical: Uploaded file URL not associated with project.

The imageURL obtained from FileService.upload is never used in the project creation payload. The project is created without any preview data, leaving the uploaded file orphaned.

Based on the backend's CreateProjectRequest schema which includes a previews field, the file information should be included in the project creation call.

🐛 Proposed fix
       const projectId = await ProjectService.create({
         project: {
           blurhash: values.blurhash,
           title: values.title,
           subTitle: values.subTitle,
           description: values.description,
           tags: values.tags,
           type: values.type,
           link: values.link,
+          previews: [
+            {
+              name: values.preview[0].name,
+              size: values.preview[0].size,
+              type: values.preview[0].type,
+              url: imageURL,
+            },
+          ],
         },
         signal: abortRef.current?.signal,
       });

Note: The exact payload structure for previews depends on what the backend expects in CreateFileRequest. Verify the required fields align with the backend DTO.

🤖 Fix all issues with AI agents
In `@apps/backend/internal/handler/v1/file.go`:
- Around line 74-76: The switch case for path == "/file/upload" calls
h.Upload(w, r) but lacks a return, causing fall-through and potential duplicate
response writes; fix by adding an immediate return after the h.Upload(w, r) call
in the handler's switch so the function exits (i.e., ensure the branch that
invokes h.Upload returns immediately to prevent executing subsequent cases).
🧹 Nitpick comments (4)
apps/backend/internal/domain/file.go (1)

189-222: Consider using isValidMimeType for file type validation.

The validation at lines 202-204 only checks for empty type strings. Consider using the isValidMimeType helper defined earlier to ensure the MIME type format is valid before sending to UploadThing.

💡 Suggested enhancement
 		if f.Type == "" {
 			return errors.New("file[" + strconv.Itoa(idx) + "]: type missing")
 		}
+		if err := isValidMimeType(f.Type); err != nil {
+			return errors.New("file[" + strconv.Itoa(idx) + "]: " + err.Error())
+		}
apps/backend/internal/handler/v1/file.go (2)

492-492: Unnecessary defer r.Body.Close().

The HTTP server automatically closes the request body after the handler returns. This defer is redundant and can be safely removed.

♻️ Suggested cleanup
-	defer r.Body.Close()
-
 	var req dto.FileUploadRequestDTO

516-516: Rename variable from image to file for consistency.

The variable name image is a leftover from the image-specific implementation. Since this is now a generic file upload, the variable should be named file or uploaded for clarity.

♻️ Suggested rename
-	image, err := h.fileRepo.Upload(r.Context(), &upload)
+	uploaded, err := h.fileRepo.Upload(r.Context(), &upload)
 	if err != nil {
 		// The error in the repo is comprehensive enough
 		// Ensure that the first letter is capitalize
@@ -526,15 +526,15 @@
 	}
 
 	file := dto.FileUploadedDTO{
-		Key:                image.Key,
-		FileName:           image.FileName,
-		FileType:           image.FileType,
-		FileUrl:            image.FileUrl,
-		ContentDisposition: image.ContentDisposition,
-		PollingJwt:         image.PollingJwt,
-		PollingUrl:         image.PollingUrl,
-		CustomId:           image.CustomId,
-		URL:                image.URL,
-		Fields:             image.Fields,
+		Key:                uploaded.Key,
+		FileName:           uploaded.FileName,
+		FileType:           uploaded.FileType,
+		FileUrl:            uploaded.FileUrl,
+		ContentDisposition: uploaded.ContentDisposition,
+		PollingJwt:         uploaded.PollingJwt,
+		PollingUrl:         uploaded.PollingUrl,
+		CustomId:           uploaded.CustomId,
+		URL:                uploaded.URL,
+		Fields:             uploaded.Fields,
 	}
apps/admin/src/types/file.ts (1)

119-161: Enforce UploadThing contract instead of silently defaulting.

ensureFields currently returns {} when fields is missing or invalid, and customId is optional. If UploadThing always returns these values, the current behavior can hide upstream contract violations. Consider throwing when fields is not an object and (if the contract is strict) making customId required or explicitly documenting why it can be missing.

♻️ Suggested tightening for ensureFields
 function ensureFields(value: unknown): { [k: string]: string } {
-  return value && typeof value === 'object'
-    ? Object.fromEntries(
-        Object.entries(value).map(([k, v]) => [
-          k,
-          ensureString({ value: v, name: k }),
-        ]),
-      )
-    : {};
+  if (!value || typeof value !== 'object') {
+    throw new Error("Expected property 'fields' to be an object");
+  }
+
+  return Object.fromEntries(
+    Object.entries(value).map(([k, v]) => [
+      k,
+      ensureString({ value: v, name: k }),
+    ]),
+  );
 }

Based on learnings, UploadThing responses include all fields (including fields and customId) and should be treated as non-null.

- Fixed broken image rendering by using the preview file URL instead of the project ID as the image source.
- Strengthened file validation by checking MIME types with `isValidMimeType` before uploading.
- Removed redundant `r.Body.Close()` defer from HTTP handlers.
- Renamed variables from `image` to `file` to reflect generic upload behavior.
- Enforced UploadThing response contract to avoid silently masking invalid payloads.
- Added missing return to prevent switch-case fall-through after handling uploads.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/admin/src/pages/project/_components/card.tsx (1)

19-25: Empty string fallback for src may cause unintended requests.

When project.previews is empty, src="" causes some browsers to request the current page URL. Consider using a placeholder image URL or conditionally rendering the <img> element.

💡 Suggested alternative: conditionally render
       <div className='relative aspect-video'>
-        <img
-          src={project.previews[0]?.url ?? ''}
-          alt={`${project.title} preview`}
-          sizes='(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw'
-          onLoad={() => setImageLoaded(true)}
-          className='absolute object-center object-cover size-full'
-        />
+        {project.previews[0]?.url && (
+          <img
+            src={project.previews[0].url}
+            alt={`${project.title} preview`}
+            sizes='(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw'
+            onLoad={() => setImageLoaded(true)}
+            className='absolute object-center object-cover size-full'
+          />
+        )}
apps/backend/internal/domain/file.go (1)

124-133: Remove UserTable from the enum or create the users table—it currently has no backing migration.

The UUID validation is appropriate for projects and educations (both use id UUID PRIMARY KEY), and the file table enforces parent_id UUID NOT NULL at the database level. However, UserTable is registered as a valid parent table in the enum but no users table exists in the database. This allows code to accept parent_table="users" with a valid UUID, creating orphaned records with no foreign key target.

Either remove UserTable from the ParentTable enum if it's not needed, create a users table migration, or add a CHECK (parent_table IN ('projects', 'education')) constraint on the file table to prevent invalid values.

🤖 Fix all issues with AI agents
In `@apps/admin/src/types/file.ts`:
- Around line 112-130: The JSDoc for ensureFields is inconsistent: it says the
function "returns an empty object if value is not a valid object" but
ensureFields actually throws an Error when value is not an object; update the
docstring to reflect the real behavior by removing the "returns an empty object"
claim and documenting that the function throws an Error when the input is not an
object or when any field value is not a string (keep references to ensureFields
and ensureString in the description and `@throws` tags).

In `@apps/backend/internal/handler/v1/file.go`:
- Around line 487-526: Validate the mapped request before calling
h.fileRepo.Upload in fileServiceHandler.Upload: after constructing
domain.FileUploadRequest (from dto.FileUploadRequestDTO -> domain.FileUpload),
run input validation (e.g., ensure req.Files is not empty, each
domain.FileUpload has non-empty Name, positive Size, valid
Type/ContentDisposition if required, and ACL/Metadata shapes as expected) and if
any check fails return http.StatusBadRequest with a concise, capitalized message
(use the same capitalization logic used for repo errors). Do not call
h.fileRepo.Upload when validation fails; only call it when the constructed
domain.FileUploadRequest passes these checks.
🧹 Nitpick comments (2)
apps/admin/src/types/file.ts (1)

7-18: Type name shadows global File interface.

The name File conflicts with the built-in DOM File interface, which could cause confusion or require explicit imports when both types are needed in the same module. Consider renaming to AppFile, DomainFile, or similar.

apps/backend/internal/domain/file.go (1)

172-239: Use snake_case JSON tags in domain models.
These structs use camelCase tags; per repo conventions, domain JSON tags should be snake_case and external API shapes should be mapped in DTOs/repo code.

Based on learnings.

- Avoided empty string image sources by conditionally rendering previews or using a safe placeholder to prevent unintended browser requests.
- Removed unsupported `UserTable` from the parent table enum to prevent creating orphaned file records without a backing users table.
- Updated JSDoc to match actual behavior where invalid payloads throw instead of returning empty objects.
- Added request-level validation for upload payloads so client errors return 400 instead of surfacing as 500s.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/backend/internal/domain/file.go`:
- Around line 226-238: The FileUploaded struct's Fields must match the
frontend's expected Record<string,string> and be required: change Fields from
map[string]any `json:"fields,omitempty"` to map[string]string `json:"fields"`
(remove omitempty) and update any server-side code that constructs FileUploaded
(where FileUploaded is created/serialized) to ensure every field value is a
string and that Fields is always present (even if empty). Also verify the
UploadThing response wiring that populates FileUploaded to convert non-string
values to strings or omit/replace them so the API never returns non-string Field
values.
♻️ Duplicate comments (3)
apps/admin/src/types/file.ts (1)

112-131: JSDoc is now consistent with implementation.

The documentation correctly describes that the function throws an error when the value is not a valid object. This addresses the previous review feedback.

apps/backend/internal/handler/v1/file.go (2)

74-76: Fixed: Return statement now present.

The missing return statement from the previous review has been added, preventing fall-through to subsequent switch cases.


487-519: Fixed: Request validation before repository call.

The handler now validates the upload request (lines 516-519) before calling h.fileRepo.Upload, returning 400 for invalid payloads instead of surfacing as 500. This addresses the previous review feedback.

🧹 Nitpick comments (2)
apps/admin/src/types/file.ts (1)

27-38: Unsafe cast before validation.

Line 31 casts the string to FileRole type before validating it in line 33, which defeats type safety. The cast should occur after validation.

♻️ Proposed fix
 function ensureRole(value: unknown): 'image' {
-  const roleValue = ensureString({
+  const roleString = ensureString({
     value,
     name: 'role',
-  }) as (typeof FileRole)[keyof typeof FileRole];
+  });
 
-  if (!Object.values(FileRole).includes(roleValue)) {
-    throw new Error(`Invalid file role: ${roleValue}`);
+  if (!Object.values(FileRole).includes(roleString as typeof FileRole[keyof typeof FileRole])) {
+    throw new Error(`Invalid file role: ${roleString}`);
   }
 
-  return roleValue;
+  return roleString as 'image';
 }
apps/backend/internal/handler/v1/file.go (1)

521-532: Consider distinguishing client vs server errors.

All repository errors return 500. If the repository can return errors for invalid external API responses (e.g., UploadThing validation failures), those would be more appropriate as 502 Bad Gateway.

♻️ Example: Distinguish upstream errors
// In repository, wrap upstream errors with a sentinel or typed error
// Then in handler:
if errors.Is(err, ErrUpstreamValidation) {
    http.Error(w, msg, http.StatusBadGateway)
    return
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/admin/src/types/file.ts`:
- Around line 103-113: The FileUpload type and mapFileUpload mapper expect
snake_case and inconsistent names; update the FileUpload type to use camelCase
property names (fileName, fileType, fileUrl, pollingJwt, pollingUrl, url)
replacing fileURL, pollingJWT, pollingURL, and URL, and then update
mapFileUpload to read those camelCase response fields (fileName, fileType,
fileUrl, pollingJwt, pollingUrl, url) instead of snake_case keys; ensure fields:
Record<string,string> and customId remain unchanged and adjust any property
assignments inside mapFileUpload (references to FileUpload and mapFileUpload)
accordingly so the shapes align with the backend's FileUploaded JSON tags.

@fingertips18
Copy link
Owner Author

@coderabbitai with the suggestions you provided, we have test workflow issues. Please resolve.

@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@fingertips18, I'll investigate the test workflow issues for you. Let me check the status of the GitHub Actions workflows for this PR.


🏁 Script executed:

#!/bin/bash
# Check the workflow runs for this PR
gh pr checks 839 --repo fingertips18/fingertips18.github.io

# Get more detailed information about failed workflow runs
gh run list --repo fingertips18/fingertips18.github.io --branch refactor/file-integration --limit 5 --json databaseId,conclusion,name,status,createdAt,event,headBranch

Length of output: 1736


Let me get the detailed logs from the failed workflow run.


🏁 Script executed:

#!/bin/bash
# Get detailed view of the failed workflow run
gh run view 21174120237 --repo fingertips18/fingertips18.github.io --log-failed

Length of output: 11103


Let me examine the failing test file to understand the test structure and provide the fix.


🏁 Script executed:

#!/bin/bash
# Read the test file to understand the failing tests
cat apps/backend/internal/handler/v1/file_test.go | grep -A 50 "zero_size_file\|empty_files_array" | head -100

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Read the entire test file to find the test structure
cat apps/backend/internal/handler/v1/file_test.go

Length of output: 18705


I've identified the issue! The tests are failing because the new validation logic in domain.FileUploadRequest.Validate() is correctly rejecting invalid inputs, but the tests were written expecting these invalid cases to succeed.

Here's the fix for the two failing test cases:

File: apps/backend/internal/handler/v1/file_test.go

1. Fix "empty files array" test (around line 204-221):

"empty files array": {
	given: Given{
		method: http.MethodPost,
		body: func() string {
			req := dto.FileUploadRequestDTO{
				Files: []dto.FileUploadDTO{},
			}
			b, _ := json.Marshal(req)
			return string(b)
		}(),
		// Remove mockRepo function - validation fails before repo is called
	},
	expected: Expected{
		code: http.StatusBadRequest,
		body: "Files missing\n",
	},
},

2. Fix "zero size file" test (around line 425-447):

"zero size file": {
	given: Given{
		method: http.MethodPost,
		body: func() string {
			req := dto.FileUploadRequestDTO{
				Files: []dto.FileUploadDTO{
					{
						Name: "empty.jpg",
						Size: 0,
						Type: "image/jpeg",
					},
				},
			}
			b, _ := json.Marshal(req)
			return string(b)
		}(),
		// Remove mockRepo function - validation fails before repo is called
	},
	expected: Expected{
		code: http.StatusBadRequest,
		body: "File[0]: size invalid\n",
	},
},

Summary of changes:

  • Changed expected.code from http.StatusAccepted (202) to http.StatusBadRequest (400)
  • Updated expected.body to match the validation error messages
  • Removed the mockRepo functions since validation now fails before the repository is called

These changes align the tests with the correct behavior: invalid inputs should be rejected with 400 status codes, not accepted.

@fingertips18
Copy link
Owner Author

@coderabbitai help for the code changes needed in order to consume the file in the form submission.

@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the form component to understand current implementation
cat apps/admin/src/pages/project/add/_components/form.tsx

Length of output: 7652


🏁 Script executed:

#!/bin/bash
# Check the Project type definition and toJSONProject function
cat apps/admin/src/types/project.ts

Length of output: 3492


🏁 Script executed:

#!/bin/bash
# Check the File type definition
cat apps/admin/src/types/file.ts

Length of output: 5099


🏁 Script executed:

#!/bin/bash
# Check the ProjectService to understand what it expects
cat apps/admin/src/services/project.ts

Length of output: 2276


🏁 Script executed:

#!/bin/bash
# Check if there are any other examples of project creation with files
rg -n "ProjectService.create" --type=ts -A 5 -B 5

Length of output: 1598


🏁 Script executed:

#!/bin/bash
# Check what FileService.upload actually returns
cat apps/admin/src/services/file.ts

Length of output: 1930


🏁 Script executed:

#!/bin/bash
# Check if there's backend documentation or handler that shows what the create project endpoint expects
cat apps/backend/internal/handler/v1/project.go | grep -A 50 "func.*Create"

Length of output: 1511


🏁 Script executed:

#!/bin/bash
# Check the backend project domain to understand the structure
cat apps/backend/internal/domain/project.go | head -100

Length of output: 2127


🏁 Script executed:

#!/bin/bash
# Check the DTO structure for CreateProjectRequest to see what fields are expected for previews
cat apps/backend/internal/handler/v1/dto.go | grep -A 20 "CreateProjectRequest"

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Also check if there's a file DTO in the project DTO
rg -n "type.*File.*struct" apps/backend/internal/handler/v1/dto.go -A 10

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check for project DTOs in the dto subdirectory
fd -e go . apps/backend/internal/handler/v1/dto/

Length of output: 142


🏁 Script executed:

#!/bin/bash
# Find and read the project DTO file
cat apps/backend/internal/handler/v1/dto/project.go

Length of output: 1283


🏁 Script executed:

#!/bin/bash
# Check the CreateFileRequest DTO structure
cat apps/backend/internal/handler/v1/dto/file.go | grep -A 15 "CreateFileRequest"

Length of output: 502


🏁 Script executed:

#!/bin/bash
# Also check FileDTO to see all fields
cat apps/backend/internal/handler/v1/dto/file.go

Length of output: 1881


@fingertips18, I've analyzed the codebase and understand the issue. The backend expects file metadata in the previews field when creating a project. Here's how to fix the form submission:

Code Changes Needed

File: apps/admin/src/pages/project/add/_components/form.tsx

Update the onSubmit function to include the uploaded file metadata in the project creation payload:

const onSubmit = async (values: Schema) => {
  let imageURL: string | null;

  try {
    setImageLoading(true);

    const preview = values.preview[0];

    const url = await FileService.upload({
      file: preview,
      signal: abortRef.current?.signal,
    });
    if (!url) {
      throw new Error('Image URL undefined');
    }

    imageURL = url;
    toast({
      level: 'success',
      title: 'Image upload complete 🎉',
      description: `${preview.name} uploaded successfully!`,
    });
  } catch {
    imageURL = null;
    toast({
      level: 'error',
      title: 'Upload failed',
      description: 'We couldn't upload your image. Please try again.',
    });
  } finally {
    setImageLoading(false);
  }

  if (!imageURL) {
    return;
  }

  // Proceed with project creation, including file metadata
  try {
    setProjectLoading(true);

    const preview = values.preview[0];

    const projectId = await ProjectService.create({
      project: {
        previews: [
          {
            role: 'image',
            name: preview.name,
            url: imageURL,
            type: preview.type,
            size: preview.size,
            // parentTable and parentID are set by backend
            parentTable: '',
            parentID: '',
          },
        ],
        blurhash: values.blurhash,
        title: values.title,
        subTitle: values.subTitle,
        description: values.description,
        tags: values.tags,
        type: values.type,
        link: values.link,
      },
      signal: abortRef.current?.signal,
    });

    if (!projectId) {
      throw new Error('Project ID undefined');
    }

    toast({
      level: 'success',
      title: 'Project upload complete 🎉',
      description: `${values.title} uploaded successfully!`,
    });

    setSubmitted(true);
    form.reset();
  } catch {
    toast({
      level: 'error',
      title: 'Upload failed',
      description: 'We couldn't upload your project. Please try again.',
    });
  } finally {
    setProjectLoading(false);
  }
};

Explanation

The key changes are:

  1. Capture file metadata: Store a reference to values.preview[0] for reuse

  2. Include previews array: Add a previews field to the project creation payload with an array containing one file object

  3. File metadata structure: Each preview object needs:

    • role: 'image' - indicates this is an image file
    • name - the original filename
    • url - the uploaded file URL from FileService
    • type - the MIME type (e.g., "image/webp")
    • size - the file size in bytes
    • parentTable and parentID - can be empty strings; the backend will override these with "project" and the created project's ID
  4. Backend handling: The backend creates the project first, then loops through the previews and creates file records with the correct parent relationship automatically.

The toJSONProject() function will properly serialize this to the snake_case format the backend expects (parent_table, parent_id, etc.).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Better alignment with project requirements and improved code clarity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant