Skip to content

feature: 인증사진 임의 업로드 기능 구현 및 게시글에 프로필 이미지, 사용자 닉네임 연결#46

Merged
FlashingFuture merged 2 commits into
mainfrom
feature/proof-image
Sep 14, 2025
Merged

feature: 인증사진 임의 업로드 기능 구현 및 게시글에 프로필 이미지, 사용자 닉네임 연결#46
FlashingFuture merged 2 commits into
mainfrom
feature/proof-image

Conversation

@FlashingFuture

Copy link
Copy Markdown
Contributor

작업 내용

  • 인증 사진을 사용자가 임의로 갤러리 등에서 업로드하도록 임시 적용
  • 커뮤니티에 사용자 프로필 이미지, 닉네임 연동

@vercel

vercel Bot commented Sep 13, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
runova-web Ready Ready Preview Comment Sep 13, 2025 8:01am

@github-actions github-actions Bot added the ✨feature 기능 label Sep 13, 2025

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Summary of Changes

Hello @FlashingFuture, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 커뮤니티 게시글 기능의 핵심적인 개선 사항을 포함합니다. 사용자가 '인증' 게시글에 단일 이미지를 직접 업로드할 수 있도록 지원하고, 모든 게시글에 작성자의 프로필 정보(닉네임, 이미지)를 명확하게 연동하여 사용자 경험을 향상시키는 데 중점을 두었습니다. 이를 통해 게시글 작성 및 조회 시 이미지와 작성자 정보가 더욱 일관성 있게 표시됩니다.

Highlights

  • 단일 이미지 업로드 기능 도입: 게시글에 여러 이미지를 첨부하는 대신 단일 이미지만 업로드할 수 있도록 API 요청 및 응답 타입이 imageUrls (배열)에서 imageUrl (단일 문자열)로 변경되었습니다. 특히 '인증' 카테고리 게시글의 경우, 사용자가 갤러리 등에서 직접 이미지를 선택하여 업로드할 수 있는 기능이 추가되었습니다.
  • 게시글 작성자 정보 연동: 게시글 목록 및 상세 페이지에서 작성자의 프로필 이미지와 닉네임을 표시하기 위해 Post 인터페이스에 authorInfo 객체가 추가되었으며, 관련 UI 컴포넌트들이 업데이트되었습니다.
  • 게시글 편집/작성 페이지 로직 개선: 게시글 편집/작성 페이지(CommunityEdit)에서 '인증' 카테고리일 때는 이미지 업로드 UI를, '공유' 카테고리일 때는 기존의 항목 선택 픽커 UI를 각각 다르게 표시하도록 로직이 분리 및 개선되었습니다. 또한, 이미지 업로드 전 미리보기 및 실제 업로드 처리 로직이 추가되었습니다.
  • API 타입 및 매퍼 함수 업데이트: 서버 응답에서 imageUrlimageUrls를 유연하게 처리하기 위한 pickImageUrl 헬퍼 함수가 추가되었고, 게시글 생성 및 업데이트 요청 시 이미지 처리 방식이 변경되었습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

이번 PR은 '인증' 게시글에 대한 이미지 업로드 기능을 도입하고, 커뮤니티 기능 전반에 작성자의 프로필 정보(닉네임, 이미지)를 연동하는 변경 사항을 담고 있습니다. Presigned URL을 이용한 이미지 업로드 로직과 authorInfo 객체를 통한 데이터 모델 개선 등 핵심적인 구현은 잘 이루어졌습니다. 다만, 빌드를 중단시킬 수 있는 치명적인 문법 오류, '공유' 게시글의 이미지가 설정되지 않는 버그, 코드 정리 및 스타일 가이드 위반 사항(하드코딩된 값, 인라인 스타일) 등 몇 가지 수정이 필요한 부분이 있습니다. 이 문제들을 해결하면 새로운 기능의 안정성과 유지보수성이 크게 향상될 것입니다.

display: grid;
place-items: center;
background: rgba(0, 0, 0, 0.35);
color: ${({ theme }) => theme.colors.surface}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

styled-componentsOverlayHint 정의에서 color 속성 뒤에 세미콜론(;)이 누락되었습니다. 이 문법 오류로 인해 애플리케이션 빌드가 실패하게 됩니다.

Suggested change
color: ${({ theme }) => theme.colors.surface}
color: ${({ theme }) => theme.colors.surface};

Comment thread apps/web/src/api/posts.ts Outdated
Comment on lines 139 to 142
const author =
'author' in res
? (res.author?.nickname ?? String(res.author?.id ?? ''))
: String((res as PostResCreate).authorId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

PostResBaseauthorInfo가 필수로 추가되면서 모든 PostRes 타입에 authorInfo가 보장됩니다. 하지만 mapPostResToEntity 함수 내 author 변수 할당 로직은 authorInfo를 사용하지 않고 과거의 복잡한 분기 처리를 그대로 유지하고 있습니다. 이로 인해 mapListItemToEntity와 로직이 달라져 혼란을 유발하고 잠재적인 버그의 원인이 될 수 있습니다.

mapListItemToEntity에서처럼 res.authorInfo.nickname을 사용하도록 로직을 단순화하는 것이 좋겠습니다.

Suggested change
const author =
'author' in res
? (res.author?.nickname ?? String(res.author?.id ?? ''))
: String((res as PostResCreate).authorId);
const author = res.authorInfo.nickname;

Comment on lines +47 to +54
const handlePick = (it: SelectableItem) => {
if (submitting) return;
onSelectItem?.(it.id);
if (category === 'SHARE') {
const rid = Number(it.id);
onChange({ routeId: Number.isNaN(rid) ? undefined : rid });
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

SHARE 카테고리에서 항목을 선택했을 때 routeIdonChange를 통해 업데이트되고, 해당 항목의 imageUrl은 업데이트되지 않고 있습니다. 이로 인해 SHARE 게시글 생성 시 imageUrl이 누락되는 버그가 발생할 수 있습니다.

handlePick 함수에서 onChange를 호출할 때, 선택된 아이템(it)의 imageUrl도 함께 전달하여 상태를 업데이트해야 합니다.

  const handlePick = (it: SelectableItem) => {
    if (submitting) return;
    onSelectItem?.(it.id);
    if (category === 'SHARE') {
      const rid = Number(it.id);
      onChange({ 
        routeId: Number.isNaN(rid) ? undefined : rid,
        imageUrl: it.imageUrl,
      });
    }
  };

Comment thread apps/web/src/api/posts.ts

// 1) presign
const presign = await getProofPresign({
type: 'verify',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

스타일 가이드에 따르면 매직 넘버나 문자열은 이름 있는 상수로 관리해야 합니다.1 'verify'라는 문자열이 하드코딩되어 있습니다. 이 값은 API 요청에 사용되는 중요한 값이므로, constants.ts와 같은 파일에 상수로 정의하여 사용하는 것이 좋습니다. 이렇게 하면 코드의 가독성과 유지보수성이 향상됩니다.

Suggested change
type: 'verify',
type: 'verify', // TODO: 상수로 분리

Style Guide References

Footnotes

  1. 상수는 하드코딩하지 말고 constants.ts 또는 theme.ts에 관리해야 합니다.

</li>
))}
</ImageList>
{imageUrl && imageUrl !== '{}' && (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

imageUrl !== '{}'와 같은 확인 로직은 컴포넌트 단이 아닌 API 데이터 매핑 계층에서 처리하는 것이 좋습니다. 서버에서 비정상적인 빈 객체 문자열을 반환하는 경우, api/posts.tspickImageUrl 함수에서 이를 처리하여 컴포넌트는 항상 정제된 imageUrl 값(유효한 URL 문자열 또는 빈 문자열)을 받도록 하는 것이 바람직합니다. 이렇게 하면 UI 컴포넌트의 책임이 명확해지고 코드가 깨끗해집니다.

type="file"
accept="image/*"
onChange={handlePickProofFile}
style={{ display: 'none' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

스타일 가이드에 따라 인라인 스타일(style={{ display: 'none' }}) 사용은 지양해야 합니다.1 이 부분은 styled-components를 사용하여 별도의 컴포넌트로 분리하거나, 기존 스타일에 속성을 추가하는 방식으로 개선하는 것이 좋습니다.

예를 들어, HiddenInput과 같은 이름의 스타일 컴포넌트를 생성하여 사용할 수 있습니다.

Style Guide References

Footnotes

  1. 인라인 스타일은 빠른 시제품용 외에는 지양해야 합니다.

@FlashingFuture FlashingFuture merged commit 209b9dc into main Sep 14, 2025
3 checks passed

@ksy90101 ksy90101 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍

Comment thread apps/web/src/api/posts.ts
title: string;
content: string;
imageUrls?: string[];
imageUrl: string; // 단일 이미지 URL

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

update를 할때는 null이 허용되는데, 생성될때는 null이 허용되지 않습니다.
이게 왜 그런걸까요?

Comment thread apps/web/src/api/posts.ts
}>;

type AuthorObj = { id: number; nickname: string; avatarUrl?: string | null };
type AuthorObj = { id: number; nickname: string; imageUrl?: string | null };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

여기 imageUrl이 왜 undefined일까요?

Comment thread apps/web/src/api/posts.ts
return res.imageUrl;
if (Array.isArray(res.imageUrls) && res.imageUrls.length > 0)
return res.imageUrls[0]!;
return '';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

null이 더 어울리지 않을까요?
혹은 에러가 더 어울리지 않을까.

실제 위 케이스가 아닌 경우가 없을꺼라는것을 명시적이여야 한다고 생각합니다.

Comment thread apps/web/src/api/posts.ts
Comment on lines +173 to +176
if (typeof query.authorId === 'number') q.push(`authorId=${query.authorId}`);
if (typeof query.routeId === 'number') q.push(`routeId=${query.routeId}`);
if (query.sort) q.push(`sort=${encodeURIComponent(query.sort)}`);
if (typeof query.limit === 'number') q.push(`limit=${query.limit}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

이쪽에서 왜 type에 따라 조건문이 필요한 이유가 무엇인가요?

Comment thread apps/web/src/api/posts.ts
Comment on lines +286 to +291
export type PresignReq = {
/** 예: 'community-proof' */
type: string;
contentType: string;
size: number;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

결국 File을 넘기는거 아닌가요?

Comment on lines 30 to 34
<ProfileHeader
userName={author}
userName={authorInfo?.nickname ?? ''}
postDate={createdAt.slice(0, 10)}
imageUrl={`https://picsum.photos/48?random=${id}`}
imageUrl={authorInfo?.imageUrl ?? ''}
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

여기서 null safe를 하샸는데 이유가 무엇인가요?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants