Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User 모델 분리 #347

Merged
merged 5 commits into from
Sep 24, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- CreateTable
CREATE TABLE `KakaoUser` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`email` VARCHAR(191) NOT NULL,
`userUuid` VARCHAR(191) NOT NULL,

UNIQUE INDEX `KakaoUser_email_key`(`email`),
UNIQUE INDEX `KakaoUser_userUuid_key`(`userUuid`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- AddForeignKey
ALTER TABLE `KakaoUser` ADD CONSTRAINT `KakaoUser_userUuid_fkey` FOREIGN KEY (`userUuid`) REFERENCES `User`(`uuid`) ON DELETE RESTRICT ON UPDATE CASCADE;

-- InsertData
INSERT INTO `KakaoUser` (`email`, `userUuid`) SELECT `email`, `uuid` FROM `User` WHERE `provider` = 'kakao';

-- DropIndex
DROP INDEX `User_email_provider_key` ON `User`;

-- AlterTable
ALTER TABLE `User` DROP COLUMN `email`,
DROP COLUMN `provider`;

-- AlterTable
ALTER TABLE `User` MODIFY `uuid` varchar(36);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- RenameColumn
ALTER TABLE `Profile` RENAME COLUMN `user_id` TO `userId`;

-- AlterTable
ALTER TABLE `Profile`
MODIFY `userId` VARCHAR(36) NOT NULL,
MODIFY `uuid` VARCHAR(36) NOT NULL,
RENAME INDEX `Profile_user_id_key` TO `Profile_userId_key`;

-- RenameForeignKey
ALTER TABLE `Profile`
DROP FOREIGN KEY `Profile_user_id_fkey`,
ADD CONSTRAINT `Profile_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`uuid`) ON DELETE CASCADE ON UPDATE CASCADE;
29 changes: 17 additions & 12 deletions nestjs-BE/server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ datasource db {
}

model User {
uuid String @id @db.VarChar(32)
email String
provider String
profiles Profile[]
refresh_tokens RefreshToken[]
@@unique([email, provider])
uuid String @id @db.VarChar(36)
kakaoUser KakaoUser?
profiles Profile[]
refresh_tokens RefreshToken[]
}

model KakaoUser {
id Int @id @default(autoincrement())
email String @unique
userUuid String @unique
user User @relation(fields: [userUuid], references: [uuid])
}

model RefreshToken {
Expand All @@ -26,12 +31,12 @@ model RefreshToken {
}

model Profile {
uuid String @id @db.VarChar(32)
user_id String @unique @db.VarChar(32)
image String
nickname String @db.VarChar(20)
user User @relation(fields: [user_id], references: [uuid], onDelete: Cascade)
spaces Profile_space[]
uuid String @id @db.VarChar(36)
userId String @unique @db.VarChar(36)
image String
nickname String @db.VarChar(20)
user User @relation(fields: [userId], references: [uuid], onDelete: Cascade)
spaces Profile_space[]
}

model Space {
Expand Down
2 changes: 1 addition & 1 deletion nestjs-BE/server/src/auth/auth.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('AuthController', () => {
usersService = module.get<UsersService>(UsersService);
});

it('kakaoLogin user have been logged in', async () => {
it('kakaoLogin', async () => {
const requestMock = { kakaoUserId: 0 };
const kakaoUserAccountMock = { email: 'kakao email' };
const tokenMock = {
Expand Down
2 changes: 1 addition & 1 deletion nestjs-BE/server/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class AuthController {
kakaoUserDto.kakaoUserId,
);
if (!kakaoUserAccount) throw new NotFoundException();
const userData = { email: kakaoUserAccount.email, provider: 'kakao' };
const userData = { email: kakaoUserAccount.email };
const user = await this.usersService.getOrCreateUser(userData);
const profileData = {
user_id: user.uuid,
Expand Down
8 changes: 4 additions & 4 deletions nestjs-BE/server/src/profiles/profiles.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class ProfilesService {
constructor(private prisma: PrismaService) {}

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

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

async getOrCreateProfile(data: CreateProfileDto): Promise<Profile> {
return this.prisma.profile.upsert({
where: { user_id: data.user_id },
where: { userId: data.user_id },
update: {},
create: {
uuid: generateUuid(),
user_id: data.user_id,
userId: data.user_id,
image: data.image,
nickname: data.nickname,
},
Expand All @@ -38,7 +38,7 @@ export class ProfilesService {
): Promise<Profile | null> {
try {
return await this.prisma.profile.update({
where: { user_id: userUuid },
where: { userId: userUuid },
data: { ...updateProfileDto },
});
} catch (err) {
Expand Down
6 changes: 0 additions & 6 deletions nestjs-BE/server/src/users/dto/create-user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
import { ApiProperty } from '@nestjs/swagger';

export class CreateUserDto {
@ApiProperty({ example: '[email protected]', description: 'email adress' })
readonly email: string;

@ApiProperty({ example: 'kakao', description: 'social site name' })
readonly provider: string;
}
72 changes: 24 additions & 48 deletions nestjs-BE/server/src/users/users.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,48 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { PrismaService } from '../prisma/prisma.service';
import generateUuid from '../utils/uuid';
import { v4 as uuid } from 'uuid';

describe('UsersService', () => {
let usersService: UsersService;
let prisma: PrismaService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: PrismaService,
useValue: {
user: {
findUnique: jest.fn(),
upsert: jest.fn(),
},
},
},
],
providers: [UsersService, PrismaService],
}).compile();

usersService = module.get<UsersService>(UsersService);
prisma = module.get<PrismaService>(PrismaService);
});

it('findUserByEmailAndProvider found user', async () => {
const testUser = {
uuid: generateUuid(),
email: '[email protected]',
provider: 'kakao',
};
jest.spyOn(prisma.user, 'findUnique').mockResolvedValue(testUser);
await prisma.kakaoUser.deleteMany({});
await prisma.user.deleteMany({});
});

const user = usersService.findUserByEmailAndProvider(
'[email protected]',
'kakao',
);
it('getOrCreateUser', async () => {
const testUserUuid = uuid();
const testEmail = '[email protected]';
const testUser = { uuid: testUserUuid };
await prisma.user.create({
data: { uuid: testUserUuid },
});
await prisma.kakaoUser.create({
data: {
email: testEmail,
userUuid: testUserUuid,
},
});

const user = usersService.getOrCreateUser({ email: testEmail });
await expect(user).resolves.toEqual(testUser);
});

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

const user = usersService.findUserByEmailAndProvider(
'[email protected]',
'kakao',
const user = await usersService.getOrCreateUser({ email: testEmail });
expect(user.uuid).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
);

await expect(user).resolves.toBeNull();
});

it('getOrCreateUser', async () => {
const testUser = {
uuid: generateUuid(),
email: '[email protected]',
provider: 'kakao',
};
jest.spyOn(prisma.user, 'upsert').mockResolvedValue(testUser);

const user = usersService.getOrCreateUser({
email: '[email protected]',
provider: 'kakao',
});

await expect(user).resolves.toEqual(testUser);
});
});
43 changes: 25 additions & 18 deletions nestjs-BE/server/src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,37 @@ import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from '@prisma/client';
import generateUuid from '../utils/uuid';
import { v4 as uuid } from 'uuid';

@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}

async findUserByEmailAndProvider(
email: string,
provider: string,
): Promise<User | null> {
return this.prisma.user.findUnique({
where: { email_provider: { email, provider } },
});
}

async getOrCreateUser(data: CreateUserDto): Promise<User> {
return this.prisma.user.upsert({
where: { email_provider: { email: data.email, provider: data.provider } },
update: {},
create: {
uuid: generateUuid(),
email: data.email,
provider: data.provider,
},
return this.prisma.$transaction(async () => {
const kakaoUser = await this.prisma.kakaoUser.findUnique({
where: { email: data.email },
});

if (!kakaoUser) {
const newUser = await this.prisma.user.create({
data: {
uuid: uuid(),
},
});
await this.prisma.kakaoUser.create({
data: {
email: data.email,
userUuid: newUser.uuid,
},
});
return newUser;
}

const user = await this.prisma.user.findUnique({
where: { uuid: kakaoUser.userUuid },
});
return user;
});
}
}
39 changes: 11 additions & 28 deletions nestjs-BE/server/test/app.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { AppModule } from '../src/app.module';
import { PrismaService } from '../src/prisma/prisma.service';
import { sign } from 'jsonwebtoken';
import { ConfigService } from '@nestjs/config';
import { v4 as uuid } from 'uuid';

describe('AppController (e2e)', () => {
let app: INestApplication;
let configService: ConfigService;
let testUserUuid: string;

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

const testUser = { email: '[email protected]', provider: 'kakao' };
prisma.user.upsert({
where: {
email_provider: { email: testUser.email, provider: testUser.provider },
},
update: {},
create: {
uuid: 'test uuid',
email: testUser.email,
provider: testUser.provider,
},
await prisma.kakaoUser.deleteMany({});
await prisma.user.deleteMany({});

testUserUuid = uuid();
await prisma.user.create({
data: { uuid: testUserUuid },
});
});

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

it('/login-test (GET) expired access token', () => {
const invalidToken = sign(
{ sub: 'test uuid' },
configService.get<string>('JWT_ACCESS_SECRET'),
{ expiresIn: '-5m' },
);

return request(app.getHttpServer())
.get('/login-test')
.auth(invalidToken, { type: 'bearer' })
.expect(HttpStatus.UNAUTHORIZED)
.expect({ message: 'Unauthorized', statusCode: 401 });
});

it('/login-test (GET) expired access token', () => {
const expiredToken = sign(
{ sub: 'test uuid' },
{ sub: testUserUuid },
configService.get<string>('JWT_ACCESS_SECRET'),
{ expiresIn: '-5m' },
);
Expand Down Expand Up @@ -99,7 +82,7 @@ describe('AppController (e2e)', () => {
});

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

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

it('/login-test (GET) logged in', async () => {
const testToken = sign(
{ sub: 'test uuid' },
{ sub: testUserUuid },
configService.get<string>('JWT_ACCESS_SECRET'),
{ expiresIn: '5m' },
);
Expand Down
Loading
Loading