Skip to content

Commit 49369c8

Browse files
authored
Merge pull request #344 from boostcampwm2023/BE-feature/login
로그인 동시성 해결
2 parents 6433aec + 2e568b8 commit 49369c8

10 files changed

+41
-116
lines changed

nestjs-BE/server/package-lock.json

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nestjs-BE/server/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"@types/node": "^20.3.1",
5353
"@types/passport-jwt": "^3.0.13",
5454
"@types/supertest": "^2.0.12",
55+
"@types/uuid": "^10.0.0",
5556
"@typescript-eslint/eslint-plugin": "^6.0.0",
5657
"@typescript-eslint/parser": "^6.0.0",
5758
"eslint": "^8.42.0",

nestjs-BE/server/src/auth/auth.controller.spec.ts

+3-29
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ describe('AuthController', () => {
2929
provide: UsersService,
3030
useValue: {
3131
findUserByEmailAndProvider: jest.fn(),
32-
createUser: jest.fn(),
32+
getOrCreateUser: jest.fn(),
3333
},
3434
},
3535
{
3636
provide: ProfilesService,
3737
useValue: {
38-
createProfile: jest.fn(),
38+
getOrCreateProfile: jest.fn(),
3939
},
4040
},
4141
{
@@ -62,7 +62,7 @@ describe('AuthController', () => {
6262
jest
6363
.spyOn(authService, 'getKakaoAccount')
6464
.mockResolvedValue(kakaoUserAccountMock);
65-
jest.spyOn(usersService, 'findUserByEmailAndProvider').mockResolvedValue({
65+
jest.spyOn(usersService, 'getOrCreateUser').mockResolvedValue({
6666
uuid: 'user uuid',
6767
} as User);
6868
jest.spyOn(authService, 'login').mockResolvedValue(tokenMock);
@@ -74,32 +74,6 @@ describe('AuthController', () => {
7474
message: 'Success',
7575
data: tokenMock,
7676
});
77-
expect(usersService.createUser).not.toHaveBeenCalled();
78-
});
79-
80-
it('kakaoLogin user login first time', async () => {
81-
const requestMock = { kakaoUserId: 0 };
82-
const kakaoUserAccountMock = { email: 'kakao email' };
83-
const tokenMock = {
84-
refresh_token: 'refresh token',
85-
access_token: 'access token',
86-
};
87-
jest
88-
.spyOn(authService, 'getKakaoAccount')
89-
.mockResolvedValue(kakaoUserAccountMock);
90-
jest
91-
.spyOn(usersService, 'createUser')
92-
.mockResolvedValue({ uuid: 'user uuid' } as User);
93-
jest.spyOn(authService, 'login').mockResolvedValue(tokenMock);
94-
95-
const response = controller.kakaoLogin(requestMock);
96-
97-
await expect(response).resolves.toEqual({
98-
statusCode: 200,
99-
message: 'Success',
100-
data: tokenMock,
101-
});
102-
expect(usersService.createUser).toHaveBeenCalled();
10377
});
10478

10579
it('kakaoLogin kakao login fail', async () => {

nestjs-BE/server/src/auth/auth.controller.ts

+9-18
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,15 @@ export class AuthController {
4141
kakaoUserDto.kakaoUserId,
4242
);
4343
if (!kakaoUserAccount) throw new NotFoundException();
44-
const email = kakaoUserAccount.email;
45-
const user = await this.usersService.findUserByEmailAndProvider(
46-
email,
47-
'kakao',
48-
);
49-
let userUuid = user?.uuid;
50-
if (!userUuid) {
51-
const data = { email, provider: 'kakao' };
52-
const createdUser = await this.usersService.createUser(data);
53-
userUuid = createdUser.uuid;
54-
const profileData = {
55-
user_id: createdUser.uuid,
56-
image: customEnv.BASE_IMAGE_URL,
57-
nickname: '익명의 사용자',
58-
};
59-
await this.profilesService.createProfile(profileData);
60-
}
61-
const tokenData = await this.authService.login(userUuid);
44+
const userData = { email: kakaoUserAccount.email, provider: 'kakao' };
45+
const user = await this.usersService.getOrCreateUser(userData);
46+
const profileData = {
47+
user_id: user.uuid,
48+
image: customEnv.BASE_IMAGE_URL,
49+
nickname: '익명의 사용자',
50+
};
51+
await this.profilesService.getOrCreateProfile(profileData);
52+
const tokenData = await this.authService.login(user.uuid);
6253
return { statusCode: 200, message: 'Success', data: tokenData };
6354
}
6455

nestjs-BE/server/src/auth/auth.service.ts

-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@ import { Injectable, UnauthorizedException } from '@nestjs/common';
22
import { JwtService } from '@nestjs/jwt';
33
import { jwtConstants, kakaoOauthConstants } from './constants';
44
import { stringify } from 'qs';
5-
import { PrismaService } from '../prisma/prisma.service';
65
import { RefreshTokensService } from './refresh-tokens.service';
76

87
@Injectable()
98
export class AuthService {
109
constructor(
1110
private jwtService: JwtService,
1211
private refreshTokensService: RefreshTokensService,
13-
protected prisma: PrismaService,
1412
) {}
1513

1614
async getKakaoAccount(kakaoUserId: number) {

nestjs-BE/server/src/profiles/profiles.service.spec.ts

+6-26
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('ProfilesService', () => {
1818
profile: {
1919
findUnique: jest.fn(),
2020
findMany: jest.fn(),
21-
create: jest.fn(),
21+
upsert: jest.fn(),
2222
update: jest.fn(),
2323
},
2424
},
@@ -83,38 +83,18 @@ describe('ProfilesService', () => {
8383
await expect(profiles).resolves.toEqual([]);
8484
});
8585

86-
it('createProfile created', async () => {
86+
it('getOrCreateProfile', async () => {
8787
const data = {
8888
user_id: generateUuid(),
8989
image: 'www.test.com/image',
9090
nickname: 'test nickname',
9191
};
92-
const testProfile = { uuid: generateUuid(), ...data };
93-
jest.spyOn(prisma.profile, 'create').mockResolvedValue(testProfile);
92+
const profileMock = { uuid: generateUuid(), ...data };
93+
jest.spyOn(prisma.profile, 'upsert').mockResolvedValue(profileMock);
9494

95-
const profile = profilesService.createProfile(data);
95+
const profile = profilesService.getOrCreateProfile(data);
9696

97-
await expect(profile).resolves.toEqual(testProfile);
98-
});
99-
100-
it("createProfile user_id doesn't exists", async () => {
101-
const data = {
102-
user_id: generateUuid(),
103-
image: 'www.test.com/image',
104-
nickname: 'test nickname',
105-
};
106-
jest
107-
.spyOn(prisma.profile, 'create')
108-
.mockRejectedValue(
109-
new PrismaClientKnownRequestError(
110-
'Foreign key constraint failed on the field: `user_id`',
111-
{ code: 'P2003', clientVersion: '' },
112-
),
113-
);
114-
115-
const profile = profilesService.createProfile(data);
116-
117-
await expect(profile).rejects.toThrow(PrismaClientKnownRequestError);
97+
await expect(profile).resolves.toEqual(profileMock);
11898
});
11999

120100
it('updateProfile updated', async () => {

nestjs-BE/server/src/profiles/profiles.service.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ export class ProfilesService {
1919
});
2020
}
2121

22-
async createProfile(data: CreateProfileDto): Promise<Profile> {
23-
return this.prisma.profile.create({
24-
data: {
22+
async getOrCreateProfile(data: CreateProfileDto): Promise<Profile> {
23+
return this.prisma.profile.upsert({
24+
where: { user_id: data.user_id },
25+
update: {},
26+
create: {
2527
uuid: generateUuid(),
2628
user_id: data.user_id,
2729
image: data.image,

nestjs-BE/server/src/users/dto/update-user.dto.ts

-12
This file was deleted.

nestjs-BE/server/src/users/users.service.spec.ts

+4-23
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Test, TestingModule } from '@nestjs/testing';
22
import { UsersService } from './users.service';
33
import { PrismaService } from '../prisma/prisma.service';
44
import generateUuid from '../utils/uuid';
5-
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
65

76
describe('UsersService', () => {
87
let usersService: UsersService;
@@ -17,7 +16,7 @@ describe('UsersService', () => {
1716
useValue: {
1817
user: {
1918
findUnique: jest.fn(),
20-
create: jest.fn(),
19+
upsert: jest.fn(),
2120
},
2221
},
2322
},
@@ -55,37 +54,19 @@ describe('UsersService', () => {
5554
await expect(user).resolves.toBeNull();
5655
});
5756

58-
it('createUser created', async () => {
57+
it('getOrCreateUser', async () => {
5958
const testUser = {
6059
uuid: generateUuid(),
6160
6261
provider: 'kakao',
6362
};
64-
jest.spyOn(prisma.user, 'create').mockResolvedValue(testUser);
63+
jest.spyOn(prisma.user, 'upsert').mockResolvedValue(testUser);
6564

66-
const user = usersService.createUser({
65+
const user = usersService.getOrCreateUser({
6766
6867
provider: 'kakao',
6968
});
7069

7170
await expect(user).resolves.toEqual(testUser);
7271
});
73-
74-
it('createUser user already exists', async () => {
75-
jest
76-
.spyOn(prisma.user, 'create')
77-
.mockRejectedValue(
78-
new PrismaClientKnownRequestError(
79-
'Unique constraint failed on the constraint: `User_email_provider_key`',
80-
{ code: 'P2025', clientVersion: '' },
81-
),
82-
);
83-
84-
const user = usersService.createUser({
85-
86-
provider: 'kakao',
87-
});
88-
89-
await expect(user).rejects.toThrow(PrismaClientKnownRequestError);
90-
});
9172
});

nestjs-BE/server/src/users/users.service.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ export class UsersService {
1717
});
1818
}
1919

20-
async createUser(data: CreateUserDto): Promise<User> {
21-
return this.prisma.user.create({
22-
data: {
20+
async getOrCreateUser(data: CreateUserDto): Promise<User> {
21+
return this.prisma.user.upsert({
22+
where: { email_provider: { email: data.email, provider: data.provider } },
23+
update: {},
24+
create: {
2325
uuid: generateUuid(),
2426
email: data.email,
2527
provider: data.provider,

0 commit comments

Comments
 (0)