Skip to content

Commit

Permalink
fix: 병합 충돌 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
seoko97 committed Nov 26, 2024
2 parents beee810 + 1a544b6 commit 0d2ff4f
Show file tree
Hide file tree
Showing 36 changed files with 636 additions and 235 deletions.
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^8.0.1",
"@nestjs/throttler": "^6.2.1",
"@nestjs/typeorm": "^10.0.2",
"@repo/types": "workspace:*",
"bcrypt": "^5.1.1",
Expand Down
7 changes: 7 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ThrottlerModule } from '@nestjs/throttler';
import { TypeOrmModule } from '@nestjs/typeorm';

import { TypeOrmConfigService } from '@/config/typeorm.config';
Expand All @@ -16,6 +17,12 @@ import { UserModule } from './user/user.module';
isGlobal: true,
envFilePath: '.env',
}),
ThrottlerModule.forRoot([
{
ttl: 60000,
limit: 100,
},
]),
AuthModule,
TicleModule,
StreamModule,
Expand Down
16 changes: 13 additions & 3 deletions apps/api/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Body, Controller, Get, Post, Res, UseGuards } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ThrottlerGuard } from '@nestjs/throttler';
import { Response } from 'express';

import { GetUserId } from '@/common/decorator/get-userId.decorator';
Expand All @@ -22,7 +23,7 @@ export class AuthController {
) {}

@Post('signup')
@ApiOperation({ summary: '회원가입' })
@ApiOperation({ summary: '로컬 회원가입' })
@ApiResponse({ status: 201, type: SignupResponseDto })
@ApiResponse({ status: 409 })
async signup(@Body() createUserDto: LocalSignupRequestDto): Promise<SignupResponseDto> {
Expand All @@ -40,6 +41,15 @@ export class AuthController {
this.cookieInsertJWT(response, userId);
}

@Post('guest/login')
@ApiOperation({ summary: '게스트 로그인' })
@ApiResponse({ status: 302, description: '홈으로 리다이렉션' })
@UseGuards(ThrottlerGuard)
async guestLogin(@Res() response: Response) {
const guestUser = await this.authService.createGuestUser();
this.cookieInsertJWT(response, guestUser.id);
}

@Get('google/login')
@ApiOperation({ summary: '구글 OAuth 로그인' })
@ApiResponse({ status: 302, description: '홈으로 리다이렉션' })
Expand Down Expand Up @@ -75,12 +85,12 @@ export class AuthController {
});
}

private async cookieInsertJWT(
private cookieInsertJWT(
response: Response,
userId: number,
redirectUrl: string = this.configService.get<string>('LOGIN_REDIRECT_URL')
) {
const { accessToken } = await this.authService.createJWT(userId);
const { accessToken } = this.authService.createJWT(userId);
this.setAuthCookie(response, accessToken);
response.redirect(redirectUrl);
}
Expand Down
22 changes: 19 additions & 3 deletions apps/api/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,24 @@ export class AuthService {
if (!isPasswordValid) {
throw new UnauthorizedException('잘못된 로그인 정보');
}
const { password, ...result } = user;
return result;
return user;
}

async createGuestUser() {
const randomNum = Math.floor(Math.random() * 10000);
const guestUser = {
username: `guest_${randomNum}`,
password: `guest_password_${randomNum}`,
email: `[email protected]`,
nickname: `guest_${randomNum}`,
introduce: `게스트 사용자입니다. `,
profileImageUrl: `https://cataas.com/cat?${Date.now()}`,
};
const user = await this.userService.findUserByUsername(guestUser.username);
if (!user) {
return this.userService.createLocalUser({ provider: 'guest', ...guestUser });
}
return user;
}

async checkSocialUser(socialUserData: CreateSocialUserDto) {
Expand All @@ -42,7 +58,7 @@ export class AuthService {
return user;
}

async createJWT(userId: number) {
createJWT(userId: number) {
const payload = { sub: userId };
return {
accessToken: this.jwtService.sign(payload),
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/ticle/ticle.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Body, Controller, Get, Param, Post, Query, UseGuards, UsePipes } from '@nestjs/common';
import { Body, Controller, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
import { CreateTicleSchema } from '@repo/types';

import { JwtAuthGuard } from '@/auth/jwt/jwt-auth.guard';
Expand Down
5 changes: 2 additions & 3 deletions apps/api/src/user/user.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { ConflictException, InternalServerErrorException } from '@nestjs/common'
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import * as bcrypt from 'bcrypt';
import { Repository } from 'typeorm';

import { User } from '@/entity/user.entity';

Expand Down Expand Up @@ -55,7 +54,7 @@ describe('UserService', () => {
});

it('should throw InternalServerErrorException on database error', async () => {
mockUserRepository.exists.mockRejectedValue(new Error('DB Error'));
mockUserRepository.exists.mockRejectedValue(new InternalServerErrorException());

await expect(service.createLocalUser(createLocalUserDto)).rejects.toThrow(
InternalServerErrorException
Expand Down Expand Up @@ -88,7 +87,7 @@ describe('UserService', () => {

it('should throw InternalServerErrorException on database error', async () => {
mockUserRepository.create.mockReturnValue(createSocialUserDto);
mockUserRepository.save.mockRejectedValue(new Error('DB Error'));
mockUserRepository.save.mockRejectedValue(new InternalServerErrorException());

await expect(service.createSocialUser(createSocialUserDto)).rejects.toThrow(
InternalServerErrorException
Expand Down
30 changes: 30 additions & 0 deletions apps/web/src/components/common/Empty/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import TicleCharacter from '@/assets/images/ticle-character.png';
import cn from '@/utils/cn';

interface EmptyProps {
title?: string;
className?: string;
imageSize?: number;
}

function Empty({ title = '항목이 비어있어요!', className, imageSize = 180 }: EmptyProps) {
return (
<div
className={cn(
'custom-dashed flex h-96 w-full flex-col items-center justify-center gap-8',
className
)}
>
<img
src={TicleCharacter}
alt="흑백 티클 캐릭터"
className="grayscale"
width={imageSize}
height={imageSize}
/>
<h1 className="text-head2 text-weak">{title}</h1>
</div>
);
}

export default Empty;
File renamed without changes.
5 changes: 0 additions & 5 deletions apps/web/src/components/common/Loading/Loading.tsx

This file was deleted.

32 changes: 32 additions & 0 deletions apps/web/src/components/common/Loading/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { cva } from 'class-variance-authority';

const dotVariants = cva('h-4 w-4 rounded-full', {
variants: {
variant: {
white: ['animate-[flashWhite_1.5s_ease-out_infinite_alternate] bg-altWeak'],
primary: ['animate-[flashPrimary_1.5s_ease-out_infinite_alternate] bg-secondary'],
},
position: {
first: '',
second: '[animation-delay:0.5s]',
third: '[animation-delay:1s]',
},
},
defaultVariants: {
variant: 'white',
},
});

type LoadingProps = {
color?: 'white' | 'primary';
};

const Loading = ({ color = 'white' }: LoadingProps) => (
<div className="flex gap-5">
<div className={dotVariants({ variant: color, position: 'first' })} />
<div className={dotVariants({ variant: color, position: 'second' })} />
<div className={dotVariants({ variant: color, position: 'third' })} />
</div>
);

export default Loading;
21 changes: 11 additions & 10 deletions apps/web/src/components/common/Tab/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { KeyboardEvent } from 'react';

export interface TabData {
name: string;
export interface TabData<T extends string> {
value: T;
label: string;
onClick: () => void;
}

interface TabProps {
tabItems: TabData[];
selectedTab: string;
interface TabProps<T extends string> {
tabItems: TabData<T>[];
selectedTab: T;
}

function Tab({ tabItems, selectedTab }: TabProps) {
function Tab<T extends string>({ tabItems, selectedTab }: TabProps<T>) {
const handleKeyDown = (e: KeyboardEvent<HTMLButtonElement>, onClick: () => void) => {
if (e.key !== 'Enter') return;
onClick();
Expand All @@ -20,15 +21,15 @@ function Tab({ tabItems, selectedTab }: TabProps) {
<div role="tablist" className="flex items-center gap-6">
{tabItems.map((tab) => (
<button
key={tab.name}
key={tab.value}
role="tab"
aria-selected={selectedTab === tab.name}
aria-selected={selectedTab === tab.value}
onClick={tab.onClick}
onKeyDown={(e) => handleKeyDown(e, tab.onClick)}
className="flex cursor-pointer flex-col gap-1.5 bg-transparent"
>
<span className="text-head1 text-main">{tab.name}</span>
<span className={`h-1 w-full ${selectedTab === tab.name ? 'bg-primary' : ''}`} />
<span className="text-head1 text-main">{tab.label}</span>
<span className={`h-1 w-full ${selectedTab === tab.value ? 'bg-primary' : ''}`} />
</button>
))}
</div>
Expand Down
16 changes: 8 additions & 8 deletions apps/web/src/components/dashboard/DashboardTab.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useNavigate } from '@tanstack/react-router';
import { useState } from 'react';
import { useMatch, useNavigate } from '@tanstack/react-router';

import Tab, { TabData } from '../common/Tab';

Expand All @@ -15,21 +14,22 @@ const DASHBOARD_ROUTES = {

function DashboardTab() {
const navigate = useNavigate();
const [selectedTab, setSelectedTab] = useState<string>(DASHBOARD_TAB.APPLIED);
const isOpenedMatch = useMatch({ from: '/dashboard/open', shouldThrow: false });
const selectedTab = isOpenedMatch ? 'OPENED' : 'APPLIED';

const DASHBOARD_TAB_DATA: TabData[] = [
const DASHBOARD_TAB_DATA: TabData<keyof typeof DASHBOARD_TAB>[] = [
{
name: DASHBOARD_TAB.APPLIED,
value: 'APPLIED',
label: DASHBOARD_TAB.APPLIED,
onClick: () => {
navigate({ to: DASHBOARD_ROUTES.APPLIED });
setSelectedTab(DASHBOARD_TAB.APPLIED);
},
},
{
name: DASHBOARD_TAB.OPENED,
value: 'OPENED',
label: DASHBOARD_TAB.OPENED,
onClick: () => {
navigate({ to: DASHBOARD_ROUTES.OPENED });
setSelectedTab(DASHBOARD_TAB.OPENED);
},
},
];
Expand Down
46 changes: 28 additions & 18 deletions apps/web/src/components/dashboard/apply/TicleInfoCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Link } from '@tanstack/react-router';
import { Link, useNavigate } from '@tanstack/react-router';
import { MouseEvent } from 'react';

import Button from '@/components/common/Button';
import { formatDateTimeRange } from '@/utils/date';
Expand All @@ -21,27 +22,36 @@ function TicleInfoCard({
status,
}: TicleInfoCardProps) {
const { dateStr, timeRangeStr } = formatDateTimeRange(startTime, endTime);
const navigate = useNavigate();

const handleTicleParticipate = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
navigate({ to: `/live/${ticleId}` });
};

return (
<div className="flex items-center justify-between rounded-lg border border-main bg-white p-6 shadow-normal">
<div className="flex gap-5">
<div className="flex items-center gap-3">
<h3 className="text-title2 text-main">개설자</h3>
<span className="text-body1 text-main">{speakerName}</span>
</div>
<div className="flex items-center gap-3">
<h3 className="text-title2 text-main">티클명</h3>
<span className="w-80 text-body1 text-main">{ticleTitle}</span>
</div>
<div className="flex items-center gap-3">
<h3 className="text-title2 text-main">진행 일시</h3>
<span className="text-body1 text-main">{`${dateStr} ${timeRangeStr}`}</span>
<Link to={`/ticle/${ticleId}`}>
<div className="flex items-center justify-between rounded-lg border border-main bg-white p-6 shadow-normal">
<div className="flex gap-5">
<div className="flex items-center gap-3">
<h3 className="text-title2 text-main">개설자</h3>
<span className="w-36 text-body1 text-main">{speakerName}</span>
</div>
<div className="flex items-center gap-3">
<h3 className="text-title2 text-main">티클명</h3>
<span className="w-80 text-body1 text-main">{ticleTitle}</span>
</div>
<div className="flex items-center gap-3">
<h3 className="text-title2 text-main">진행 일시</h3>
<span className="text-body1 text-main">{`${dateStr} ${timeRangeStr}`}</span>
</div>
</div>

<Button disabled={status === 'closed'} onClick={handleTicleParticipate}>
티클 참여하기
</Button>
</div>
<Link to={`/live/${ticleId}`}>
<Button disabled={status === 'closed'}>티클 참여하기</Button>
</Link>
</div>
</Link>
);
}

Expand Down
Loading

0 comments on commit 0d2ff4f

Please sign in to comment.