프론트엔드백엔드
프로필 수정 + Header 아바타 + MateBoard 본인글 표시 — POP-SPOT v2.16
동
김동현v2.15.3 MY 탭 내 계정 카드의 자연스러운 다음 단계. 프로필 편집 페이지 신설, Header 우상단 작은 아바타, 메이트 게시판에서 내가 쓴 글에 본인 배지 표시. 3 곳을 같이 정리.
v2.15.3 에서 MY 탭에 내 계정 카드를 만들고 보니 "편집" 링크가 열 페이지가 없었다. 프로필 편집 페이지 + Header 우상단 아바타 + 메이트 게시판 본인글 표시를 같이 정비.
이 글에서 다루는 것
모르는 단어 한 줄로
| 용어 | 한 줄 설명 |
|---|---|
| presigned URL | 일정 시간 동안만 유효한 업로드 URL. 클라이언트가 서버 거치지 않고 S3 같은 곳에 직접 올릴 때 씀 |
| multipart/form-data | 파일과 텍스트 필드를 같이 보낼 때 쓰는 HTTP 요청 형식 |
| 본인 배지 | 게시글 작성자가 현재 로그인한 사람과 같을 때 "내 글" 표시를 붙여주는 작은 라벨 |
무엇이 바뀌었나
| 영역 | v2.15 | v2.16 |
|---|---|---|
| 프로필 편집 | 없음 | <code>/profile/edit</code> 페이지 — 이름·프로필 사진 변경 |
| Header 우상단 | 로그인/로그아웃 텍스트만 | 아바타 + 닉네임(md+) + PRO 뱃지(Premium) |
| MateBoard | 모든 글이 동등 | 본인 글에 "내 글" 라임 배지 |
1. 프로필 편집 페이지
백엔드 — UserProfileController
파일: popspot-backend/src/main/java/com/popspot/controller/UserProfileController.java (v2.16 신규)
java
// v2.16 — UserProfileController.java (신규)
@RestController
@RequestMapping("/api/v1/users/me/profile")
public class UserProfileController {
private final UserProfileService service;
public UserProfileController(UserProfileService service) {
this.service = service;
}
@PatchMapping
public UserProfileResponse updateProfile(
Authentication auth,
@RequestBody @Valid ProfileUpdateRequest req) {
Long userId = Long.parseLong(auth.getName());
return service.updateProfile(userId, req);
}
@PostMapping(value = "/image",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public UserProfileResponse updateImage(
Authentication auth,
@RequestPart("file") MultipartFile file) {
Long userId = Long.parseLong(auth.getName());
return service.updateProfileImage(userId, file);
}
}해석 — 한 줄씩
백엔드 — UserProfileService
파일: popspot-backend/src/main/java/com/popspot/service/UserProfileService.java
java
// v2.16 — UserProfileService.updateProfile()
@Transactional
public UserProfileResponse updateProfile(Long userId, ProfileUpdateRequest req) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new ResourceNotFoundException("User"));
if (req.name() != null) {
String safe = req.name().trim();
if (safe.length() < 2 || safe.length() > 20) {
throw new BadRequestException("이름은 2~20자");
}
user.changeName(safe);
}
return UserProfileResponse.from(user);
}해석
프론트 — 폼
파일: popspot-frontend/src/app/profile/edit/page.tsx (v2.16 신규)
typescript
// v2.16 — profile/edit/page.tsx (신규)
'use client';
export default function ProfileEditPage() {
const [name, setName] = useState('');
const [file, setFile] = useState<File | null>(null);
async function handleSave() {
await fetch('/api/v1/users/me/profile', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ name }),
});
if (file) {
const fd = new FormData();
fd.append('file', file);
await fetch('/api/v1/users/me/profile/image', {
method: 'POST',
credentials: 'include',
body: fd,
});
}
router.push('/?tab=MY');
}
}해석
2. Header 우상단 아바타
코드
파일: popspot-frontend/src/components/layout/Header.tsx
typescript
// v2.16 — Header.tsx 우상단 아바타 블록
{user && (
<div className="flex items-center gap-2">
<Avatar src={user.profileImageUrl} name={user.name} size={28} />
<span className="hidden md:inline-flex text-sm font-medium">
{user.name}
</span>
{user.isPremium && (
<span className="hidden md:inline-flex text-[10px] px-1.5 py-0.5
rounded bg-amber-400/20 text-amber-700">
PRO
</span>
)}
</div>
)}해석
디자인 결정
v2.15 까지 UserChip 이 hidden md:inline-flex 였다 → 모바일에서 본인 정보 자체가 안 보임. 모바일이 70% 이상인 트래픽이라 이건 사용성 문제. 아바타만이라도 항상 보이게.
3. MateBoard 본인 글 표시
코드
파일: popspot-frontend/src/features/mate/MateBoard.tsx (각 게시글 카드 렌더링)
typescript
// v2.16 — MateBoard.tsx 내 각 카드 렌더링 쪽
{post.authorId === currentUserId && (
<span className="inline-flex items-center gap-1 text-[10px]
px-2 py-0.5 rounded-full
bg-lime-400/15 text-lime-700 font-medium">
내 글
</span>
)}해석
왜 필요한가
메이트 게시판은 자기 글을 한 번 올리고 잊는 패턴이 많다. 며칠 뒤 게시판 들어와서 "내가 뭘 썼었더라" 를 찾을 때 일일이 클릭해서 확인해야 했음. 본인 배지가 있으면 한눈에 식별.
관련 글
이전 글robots.txt 정적 전환 + middleware deep link 우회 + MY 탭 내 계정 카드 — POP-SPOT v2.15 (~v2.15.3)다음 글SearchBox 에 옛 row 가 노출되던 문제 — confidence ≥ 0.8 인덱스 가드 + 자동수집 95 키워드/2회/Geocoding 자동화 — POP-SPOT v2.13 (+v2.13.1~v2.13.3.1)
관련 글
인트로 페이지 제거 → 루트가 메인 직행 + AuthGuard 스피너 게이트 제거로 SEO 색인 해결 — POP-SPOT v2.23 (+v2.23.1/2)발견형 서비스인데 모든 방문자가 5 섭션 인트로→ENTER 거쳐야 메인 도달. 이탈을 키우는 구조라 제거. v2.23.1·2 은 SEO 색인 이시합 해결 — /popups/[slug] 이 크롤러를 /login 으로 튕긴게 수정하고, AuthGuard 의 스피너 게이트를 제거해 공개 페이지가 실제 HTML로 서버 렌더링 되도록.전면 보안 감사 + 수정 — IDOR 5 곳 · 저장형 XSS · 메모리/스토리지 누수 · 클린코드 점검 — POP-SPOT v2.22인증·인가·인젝션·암호화·프론트·컴플라이언스 6 영역 병렬 감사. C1 IDOR (Stamp/MyPage/Mate/ChatFile) + C2 저장형 XSS (카카오 로드뷰 오버레이) + H1 인증 없는 업로드 + H2/H3 무한 증가 인메모리 맵 + H4 SSE emitter 누수 + H5 토큰 노출. 동시에 운영 점검 4 건 (신고 어뻐징 · 크롤링 입력 정제 · 이메일 열거 · 백업 하드닝).Spotify OAuth + Web Playback SDK 통합 — 3-tier 재생 엔진 (Premium SDK / iTunes preview / YouTube) — POP-SPOT v2.21 S10~S18Spotify OAuth 백엔드(AES-256-GCM 토큰 암호화) + Web Playback SDK 프론트 통합 + iTunes preview 폴백 + YouTube 폴백의 3-tier 재생 엔진. Premium 은 320kbps 풀트랙, Free/미연결은 30~90초 preview, 외엔 YouTube 폴백. S17/S18 에서 운영 버그 네 건 추가 수정.