Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 30 additions & 9 deletions src/features/user/repositories/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,39 @@ export class UserRepository {
async updateProfile(args: {
accountId: bigint;
nickname?: string;
name?: string;
birthDate?: Date | null;
phoneNumber?: string | null;
}): Promise<void> {
await this.prisma.userProfile.update({
where: { account_id: args.accountId },
data: {
...(args.nickname !== undefined ? { nickname: args.nickname } : {}),
...(args.birthDate !== undefined ? { birth_date: args.birthDate } : {}),
...(args.phoneNumber !== undefined
? { phone_number: args.phoneNumber }
: {}),
},
const hasName = args.name !== undefined;
const hasProfileFields =
args.nickname !== undefined ||
args.birthDate !== undefined ||
args.phoneNumber !== undefined;

// name은 account 테이블, 나머지는 user_profile 테이블이라
// 두 테이블 부분 실패 방지를 위해 transaction으로 묶는다.
await this.prisma.$transaction(async (tx) => {
if (hasName) {
await tx.account.update({
where: { id: args.accountId },
data: { name: args.name },
});
}
if (hasProfileFields) {
await tx.userProfile.update({
where: { account_id: args.accountId },
data: {
...(args.nickname !== undefined ? { nickname: args.nickname } : {}),
...(args.birthDate !== undefined
? { birth_date: args.birthDate }
: {}),
...(args.phoneNumber !== undefined
? { phone_number: args.phoneNumber }
: {}),
},
});
}
});
}

Expand Down
92 changes: 92 additions & 0 deletions src/features/user/services/user-profile.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,98 @@ describe('UserProfileService (real DB)', () => {
service.updateMyProfile(me.id, { nickname: 'taken' }),
).rejects.toThrow(ConflictException);
});

it('name만 단독 업데이트하면 Account.name이 갱신된다', async () => {
const account = await createAccount(prisma, {
account_type: 'USER',
name: '구이름',
});
await createUserProfile(prisma, { account_id: account.id });

const result = await service.updateMyProfile(account.id, {
name: '새이름',
});

expect(result.name).toBe('새이름');
const saved = await prisma.account.findUniqueOrThrow({
where: { id: account.id },
});
expect(saved.name).toBe('새이름');
});

it('name 입력 시 trim 후 저장한다', async () => {
const account = await createAccount(prisma, {
account_type: 'USER',
name: '구이름',
});
await createUserProfile(prisma, { account_id: account.id });

const result = await service.updateMyProfile(account.id, {
name: ' 홍길동 ',
});

expect(result.name).toBe('홍길동');
});

it('name이 빈 문자열이면 BadRequestException', async () => {
const account = await createAccount(prisma, {
account_type: 'USER',
name: '구이름',
});
await createUserProfile(prisma, { account_id: account.id });

await expect(
service.updateMyProfile(account.id, { name: '' }),
).rejects.toThrow(BadRequestException);
});

it('name이 공백-only이면 BadRequestException', async () => {
const account = await createAccount(prisma, {
account_type: 'USER',
name: '구이름',
});
await createUserProfile(prisma, { account_id: account.id });

await expect(
service.updateMyProfile(account.id, { name: ' ' }),
).rejects.toThrow(BadRequestException);
});

it('name + nickname 동시 업데이트 시 둘 다 반영된다', async () => {
const account = await createAccount(prisma, {
account_type: 'USER',
name: '구이름',
});
await createUserProfile(prisma, {
account_id: account.id,
nickname: 'oldNick',
});

const result = await service.updateMyProfile(account.id, {
name: '새이름',
nickname: 'newNick',
});

expect(result.name).toBe('새이름');
expect(result.profile.nickname).toBe('newNick');
});

it('name 미지정 시 기존 Account.name이 유지된다', async () => {
const account = await createAccount(prisma, {
account_type: 'USER',
name: '유지될이름',
});
await createUserProfile(prisma, { account_id: account.id });

await service.updateMyProfile(account.id, {
nickname: 'newNick',
});

const saved = await prisma.account.findUniqueOrThrow({
where: { id: account.id },
});
expect(saved.name).toBe('유지될이름');
});
});

// ─── updateMyProfileImage ───
Expand Down
14 changes: 13 additions & 1 deletion src/features/user/services/user-profile.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@ export class UserProfileService extends UserBaseService {
await this.requireActiveUser(accountId);

const hasNickname = input.nickname !== undefined;
const hasName = input.name !== undefined;
const hasBirthDate = input.birthDate !== undefined;
const hasPhoneNumber = input.phoneNumber !== undefined;

if (!hasNickname && !hasBirthDate && !hasPhoneNumber) {
if (!hasNickname && !hasName && !hasBirthDate && !hasPhoneNumber) {
throw new BadRequestException('No fields to update.');
}

Expand All @@ -89,6 +90,16 @@ export class UserProfileService extends UserBaseService {
if (isTaken) throw new ConflictException('Nickname already exists.');
}

// figma 명세: 이름은 필수값. 전송되었지만 trim 후 빈 문자열이면 reject.
let name: string | undefined = undefined;
if (hasName) {
const normalized = this.normalizeName(input.name);
if (!normalized) {
throw new BadRequestException('Name cannot be empty.');
}
name = normalized;
}

const birthDate = hasBirthDate
? this.normalizeBirthDate(input.birthDate)
: undefined;
Expand All @@ -99,6 +110,7 @@ export class UserProfileService extends UserBaseService {
await this.repo.updateProfile({
accountId,
...(hasNickname ? { nickname } : {}),
...(hasName ? { name } : {}),
...(hasBirthDate ? { birthDate } : {}),
...(hasPhoneNumber ? { phoneNumber } : {}),
});
Expand Down
1 change: 1 addition & 0 deletions src/features/user/types/user-input.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface CompleteOnboardingInput {

export interface UpdateMyProfileInput {
nickname?: string | null;
name?: string | null;
birthDate?: Date | null;
phoneNumber?: string | null;
}
Expand Down
2 changes: 2 additions & 0 deletions src/features/user/user-profile.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ input CompleteOnboardingInput {
input UpdateMyProfileInput {
"""닉네임"""
nickname: String
"""이름(전송 시 trim 후 비어있으면 reject)"""
name: String
"""생년월일"""
birthDate: DateTime
"""전화번호"""
Expand Down
Loading