Skip to content

Commit eb7c474

Browse files
authored
Merge pull request #347 from boostcampwm2023/BE-feature/login
User 모델 분리
2 parents b1f9419 + 39dfd5a commit eb7c474

File tree

12 files changed

+214
-165
lines changed

12 files changed

+214
-165
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
-- CreateTable
2+
CREATE TABLE `KakaoUser` (
3+
`id` INTEGER NOT NULL AUTO_INCREMENT,
4+
`email` VARCHAR(191) NOT NULL,
5+
`userUuid` VARCHAR(191) NOT NULL,
6+
7+
UNIQUE INDEX `KakaoUser_email_key`(`email`),
8+
UNIQUE INDEX `KakaoUser_userUuid_key`(`userUuid`),
9+
PRIMARY KEY (`id`)
10+
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
11+
12+
-- AddForeignKey
13+
ALTER TABLE `KakaoUser` ADD CONSTRAINT `KakaoUser_userUuid_fkey` FOREIGN KEY (`userUuid`) REFERENCES `User`(`uuid`) ON DELETE RESTRICT ON UPDATE CASCADE;
14+
15+
-- InsertData
16+
INSERT INTO `KakaoUser` (`email`, `userUuid`) SELECT `email`, `uuid` FROM `User` WHERE `provider` = 'kakao';
17+
18+
-- DropIndex
19+
DROP INDEX `User_email_provider_key` ON `User`;
20+
21+
-- AlterTable
22+
ALTER TABLE `User` DROP COLUMN `email`,
23+
DROP COLUMN `provider`;
24+
25+
-- AlterTable
26+
ALTER TABLE `User` MODIFY `uuid` varchar(36);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- RenameColumn
2+
ALTER TABLE `Profile` RENAME COLUMN `user_id` TO `userId`;
3+
4+
-- AlterTable
5+
ALTER TABLE `Profile`
6+
MODIFY `userId` VARCHAR(36) NOT NULL,
7+
MODIFY `uuid` VARCHAR(36) NOT NULL,
8+
RENAME INDEX `Profile_user_id_key` TO `Profile_userId_key`;
9+
10+
-- RenameForeignKey
11+
ALTER TABLE `Profile`
12+
DROP FOREIGN KEY `Profile_user_id_fkey`,
13+
ADD CONSTRAINT `Profile_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`uuid`) ON DELETE CASCADE ON UPDATE CASCADE;

nestjs-BE/server/prisma/schema.prisma

+17-12
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@ datasource db {
88
}
99

1010
model User {
11-
uuid String @id @db.VarChar(32)
12-
email String
13-
provider String
14-
profiles Profile[]
15-
refresh_tokens RefreshToken[]
16-
@@unique([email, provider])
11+
uuid String @id @db.VarChar(36)
12+
kakaoUser KakaoUser?
13+
profiles Profile[]
14+
refresh_tokens RefreshToken[]
15+
}
16+
17+
model KakaoUser {
18+
id Int @id @default(autoincrement())
19+
email String @unique
20+
userUuid String @unique
21+
user User @relation(fields: [userUuid], references: [uuid])
1722
}
1823

1924
model RefreshToken {
@@ -26,12 +31,12 @@ model RefreshToken {
2631
}
2732

2833
model Profile {
29-
uuid String @id @db.VarChar(32)
30-
user_id String @unique @db.VarChar(32)
31-
image String
32-
nickname String @db.VarChar(20)
33-
user User @relation(fields: [user_id], references: [uuid], onDelete: Cascade)
34-
spaces Profile_space[]
34+
uuid String @id @db.VarChar(36)
35+
userId String @unique @db.VarChar(36)
36+
image String
37+
nickname String @db.VarChar(20)
38+
user User @relation(fields: [userId], references: [uuid], onDelete: Cascade)
39+
spaces Profile_space[]
3540
}
3641

3742
model Space {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe('AuthController', () => {
5454
usersService = module.get<UsersService>(UsersService);
5555
});
5656

57-
it('kakaoLogin user have been logged in', async () => {
57+
it('kakaoLogin', async () => {
5858
const requestMock = { kakaoUserId: 0 };
5959
const kakaoUserAccountMock = { email: 'kakao email' };
6060
const tokenMock = {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class AuthController {
4242
kakaoUserDto.kakaoUserId,
4343
);
4444
if (!kakaoUserAccount) throw new NotFoundException();
45-
const userData = { email: kakaoUserAccount.email, provider: 'kakao' };
45+
const userData = { email: kakaoUserAccount.email };
4646
const user = await this.usersService.getOrCreateUser(userData);
4747
const profileData = {
4848
user_id: user.uuid,

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class ProfilesService {
1010
constructor(private prisma: PrismaService) {}
1111

1212
async findProfile(userUuid: string): Promise<Profile | null> {
13-
return this.prisma.profile.findUnique({ where: { user_id: userUuid } });
13+
return this.prisma.profile.findUnique({ where: { userId: userUuid } });
1414
}
1515

1616
async findProfiles(profileUuids: string[]): Promise<Profile[]> {
@@ -21,11 +21,11 @@ export class ProfilesService {
2121

2222
async getOrCreateProfile(data: CreateProfileDto): Promise<Profile> {
2323
return this.prisma.profile.upsert({
24-
where: { user_id: data.user_id },
24+
where: { userId: data.user_id },
2525
update: {},
2626
create: {
2727
uuid: generateUuid(),
28-
user_id: data.user_id,
28+
userId: data.user_id,
2929
image: data.image,
3030
nickname: data.nickname,
3131
},
@@ -38,7 +38,7 @@ export class ProfilesService {
3838
): Promise<Profile | null> {
3939
try {
4040
return await this.prisma.profile.update({
41-
where: { user_id: userUuid },
41+
where: { userId: userUuid },
4242
data: { ...updateProfileDto },
4343
});
4444
} catch (err) {
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
import { ApiProperty } from '@nestjs/swagger';
2-
31
export class CreateUserDto {
4-
@ApiProperty({ example: '[email protected]', description: 'email adress' })
52
readonly email: string;
6-
7-
@ApiProperty({ example: 'kakao', description: 'social site name' })
8-
readonly provider: string;
93
}
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,48 @@
11
import { Test, TestingModule } from '@nestjs/testing';
22
import { UsersService } from './users.service';
33
import { PrismaService } from '../prisma/prisma.service';
4-
import generateUuid from '../utils/uuid';
4+
import { v4 as uuid } from 'uuid';
55

66
describe('UsersService', () => {
77
let usersService: UsersService;
88
let prisma: PrismaService;
99

1010
beforeEach(async () => {
1111
const module: TestingModule = await Test.createTestingModule({
12-
providers: [
13-
UsersService,
14-
{
15-
provide: PrismaService,
16-
useValue: {
17-
user: {
18-
findUnique: jest.fn(),
19-
upsert: jest.fn(),
20-
},
21-
},
22-
},
23-
],
12+
providers: [UsersService, PrismaService],
2413
}).compile();
2514

2615
usersService = module.get<UsersService>(UsersService);
2716
prisma = module.get<PrismaService>(PrismaService);
28-
});
2917

30-
it('findUserByEmailAndProvider found user', async () => {
31-
const testUser = {
32-
uuid: generateUuid(),
33-
34-
provider: 'kakao',
35-
};
36-
jest.spyOn(prisma.user, 'findUnique').mockResolvedValue(testUser);
18+
await prisma.kakaoUser.deleteMany({});
19+
await prisma.user.deleteMany({});
20+
});
3721

38-
const user = usersService.findUserByEmailAndProvider(
39-
40-
'kakao',
41-
);
22+
it('getOrCreateUser', async () => {
23+
const testUserUuid = uuid();
24+
const testEmail = '[email protected]';
25+
const testUser = { uuid: testUserUuid };
26+
await prisma.user.create({
27+
data: { uuid: testUserUuid },
28+
});
29+
await prisma.kakaoUser.create({
30+
data: {
31+
email: testEmail,
32+
userUuid: testUserUuid,
33+
},
34+
});
4235

36+
const user = usersService.getOrCreateUser({ email: testEmail });
4337
await expect(user).resolves.toEqual(testUser);
4438
});
4539

46-
it('findUserByEmailAndProvider not found user', async () => {
47-
jest.spyOn(prisma.user, 'findUnique').mockResolvedValue(null);
40+
it("getOrCreateUser user doesn't exist", async () => {
41+
const testEmail = '[email protected]';
4842

49-
const user = usersService.findUserByEmailAndProvider(
50-
51-
'kakao',
43+
const user = await usersService.getOrCreateUser({ email: testEmail });
44+
expect(user.uuid).toMatch(
45+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
5246
);
53-
54-
await expect(user).resolves.toBeNull();
55-
});
56-
57-
it('getOrCreateUser', async () => {
58-
const testUser = {
59-
uuid: generateUuid(),
60-
61-
provider: 'kakao',
62-
};
63-
jest.spyOn(prisma.user, 'upsert').mockResolvedValue(testUser);
64-
65-
const user = usersService.getOrCreateUser({
66-
67-
provider: 'kakao',
68-
});
69-
70-
await expect(user).resolves.toEqual(testUser);
7147
});
7248
});

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

+25-18
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,37 @@ import { Injectable } from '@nestjs/common';
22
import { PrismaService } from '../prisma/prisma.service';
33
import { CreateUserDto } from './dto/create-user.dto';
44
import { User } from '@prisma/client';
5-
import generateUuid from '../utils/uuid';
5+
import { v4 as uuid } from 'uuid';
66

77
@Injectable()
88
export class UsersService {
99
constructor(private prisma: PrismaService) {}
1010

11-
async findUserByEmailAndProvider(
12-
email: string,
13-
provider: string,
14-
): Promise<User | null> {
15-
return this.prisma.user.findUnique({
16-
where: { email_provider: { email, provider } },
17-
});
18-
}
19-
2011
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: {
25-
uuid: generateUuid(),
26-
email: data.email,
27-
provider: data.provider,
28-
},
12+
return this.prisma.$transaction(async () => {
13+
const kakaoUser = await this.prisma.kakaoUser.findUnique({
14+
where: { email: data.email },
15+
});
16+
17+
if (!kakaoUser) {
18+
const newUser = await this.prisma.user.create({
19+
data: {
20+
uuid: uuid(),
21+
},
22+
});
23+
await this.prisma.kakaoUser.create({
24+
data: {
25+
email: data.email,
26+
userUuid: newUser.uuid,
27+
},
28+
});
29+
return newUser;
30+
}
31+
32+
const user = await this.prisma.user.findUnique({
33+
where: { uuid: kakaoUser.userUuid },
34+
});
35+
return user;
2936
});
3037
}
3138
}

nestjs-BE/server/test/app.e2e-spec.ts

+11-28
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import { AppModule } from '../src/app.module';
55
import { PrismaService } from '../src/prisma/prisma.service';
66
import { sign } from 'jsonwebtoken';
77
import { ConfigService } from '@nestjs/config';
8+
import { v4 as uuid } from 'uuid';
89

910
describe('AppController (e2e)', () => {
1011
let app: INestApplication;
1112
let configService: ConfigService;
13+
let testUserUuid: string;
1214

1315
beforeAll(async () => {
1416
const moduleFixture: TestingModule = await Test.createTestingModule({
@@ -23,17 +25,12 @@ describe('AppController (e2e)', () => {
2325
const prisma: PrismaService =
2426
moduleFixture.get<PrismaService>(PrismaService);
2527

26-
const testUser = { email: '[email protected]', provider: 'kakao' };
27-
prisma.user.upsert({
28-
where: {
29-
email_provider: { email: testUser.email, provider: testUser.provider },
30-
},
31-
update: {},
32-
create: {
33-
uuid: 'test uuid',
34-
email: testUser.email,
35-
provider: testUser.provider,
36-
},
28+
await prisma.kakaoUser.deleteMany({});
29+
await prisma.user.deleteMany({});
30+
31+
testUserUuid = uuid();
32+
await prisma.user.create({
33+
data: { uuid: testUserUuid },
3734
});
3835
});
3936

@@ -55,23 +52,9 @@ describe('AppController (e2e)', () => {
5552
.expect({ message: 'Unauthorized', statusCode: 401 });
5653
});
5754

58-
it('/login-test (GET) expired access token', () => {
59-
const invalidToken = sign(
60-
{ sub: 'test uuid' },
61-
configService.get<string>('JWT_ACCESS_SECRET'),
62-
{ expiresIn: '-5m' },
63-
);
64-
65-
return request(app.getHttpServer())
66-
.get('/login-test')
67-
.auth(invalidToken, { type: 'bearer' })
68-
.expect(HttpStatus.UNAUTHORIZED)
69-
.expect({ message: 'Unauthorized', statusCode: 401 });
70-
});
71-
7255
it('/login-test (GET) expired access token', () => {
7356
const expiredToken = sign(
74-
{ sub: 'test uuid' },
57+
{ sub: testUserUuid },
7558
configService.get<string>('JWT_ACCESS_SECRET'),
7659
{ expiresIn: '-5m' },
7760
);
@@ -99,7 +82,7 @@ describe('AppController (e2e)', () => {
9982
});
10083

10184
it('/login-test (GET) wrong secret access token', () => {
102-
const invalidToken = sign({ sub: 'test uuid' }, 'wrong jwt access token', {
85+
const invalidToken = sign({ sub: testUserUuid }, 'wrong jwt access token', {
10386
expiresIn: '5m',
10487
});
10588

@@ -112,7 +95,7 @@ describe('AppController (e2e)', () => {
11295

11396
it('/login-test (GET) logged in', async () => {
11497
const testToken = sign(
115-
{ sub: 'test uuid' },
98+
{ sub: testUserUuid },
11699
configService.get<string>('JWT_ACCESS_SECRET'),
117100
{ expiresIn: '5m' },
118101
);

0 commit comments

Comments
 (0)