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

[강지은] 인스타 스토리 API #6

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
6 changes: 6 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,11 @@ module.exports = {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ volumes:
services:
db:
image: mysql/mysql-server:8.0
container_name: nest-dev-db
container_name: nest2-insta-story
restart: always
command:
- --default-authentication-plugin=mysql_native_password
Expand All @@ -17,8 +17,8 @@ services:
MYSQL_ROOT_PASSWORD: password
MYSQL_USER: user
MYSQL_PASSWORD: password
MYSQL_DATABASE: nest_db
MYSQL_DATABASE: nest2_insta_story
ports:
- '3306:3306'
volumes:
- db_vol:/var/lib/mysql
- db_vol:/var/lib/mysql
7 changes: 4 additions & 3 deletions template/package/package.typeorm.json → package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^7.1.13",
"@nestjs/swagger": "^7.3.1",
"@nestjs/typeorm": "^10.0.0",
"@types/mime": "^4.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"dotenv": "^16.3.1",
Expand All @@ -41,15 +42,15 @@
"@nestjs/schematics": "^10.0.0",
"@nestjs/testing": "^10.2.7",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/jest": "^29.5.12",
"@types/node": "^20.3.1",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"jest": "^29.7.0",
"prettier": "^3.0.0",
"source-map-support": "^0.5.21",
"supertest": "^6.3.3",
Expand Down
5 changes: 5 additions & 0 deletions template/source/typeorm/app.module.ts → src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule } from '@nestjs/config';
import { addTransactionalDataSource } from 'typeorm-transactional';
import { DataSource } from 'typeorm';
import { StoryModule } from './story/story.module';
import { Story } from './story/entities/story.entity';
import { Hashtag } from './story/entities/hashtag.entity';

@Module({
imports: [
Expand All @@ -17,6 +20,7 @@ import { DataSource } from 'typeorm';
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
synchronize: process.env.DB_SYNC === 'true',
entities: [Story, Hashtag],
timezone: 'Z',
};
},
Expand All @@ -28,6 +32,7 @@ import { DataSource } from 'typeorm';
return addTransactionalDataSource(new DataSource(options));
},
}),
StoryModule,
],
controllers: [],
providers: [],
Expand Down
41 changes: 41 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { initializeTransactionalContext } from 'typeorm-transactional';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
initializeTransactionalContext();

const app = await NestFactory.create(AppModule);

// 파이프라인 설정
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);

// Swagger 적용
const config = new DocumentBuilder()
.setTitle('insta-story Api')
.setDescription('어쩌다 nest 2주차')
.setVersion('1.0')
.build();

const swaggerOptions = {
operationIdFactory: (controllerKey: string, methodKey: string) => methodKey,
};

const document = SwaggerModule.createDocument(app, config, swaggerOptions);
SwaggerModule.setup('api', app, document);

// TODO: 프로그램 구현
await app.listen(process.env.PORT || 8000);

console.log(`Application is running on: ${await app.getUrl()}`);
}

bootstrap();
9 changes: 9 additions & 0 deletions src/page/pagination.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Type } from 'class-transformer';

export class PaginationDto {
@Type(() => Number)
page: number = 1;

@Type(() => Number)
limit: number = 10;
}
23 changes: 23 additions & 0 deletions src/page/pagination.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export class PaginationResult<T> {
data: T[];
page: number;
limit: number;
totalCount: number;
totalPage: number;

constructor(data: T[], page: number, limit: number, totalCount: number) {
this.data = data;
this.page = page;
this.totalPage = Math.ceil(totalCount / limit);
this.limit = limit;
}
}

export function createPaginationResult<T>(
data: T[],
page: number,
totalPage: number,
limit: number,
): PaginationResult<T> {
return new PaginationResult(data, page, limit, totalPage);
}
12 changes: 12 additions & 0 deletions src/story/dto/create-story-request.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Hashtag } from './../entities/hashtag.entity';
export class CreateStoryRequestDto {
title: string;

author: string;

validTime: number;

image: string;

hashtags: Hashtag[];
}
24 changes: 24 additions & 0 deletions src/story/dto/create-story-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Hashtag } from './../entities/hashtag.entity';
import { Story } from '../entities/story.entity';

export class CreateStoryResponseDto {
id: number;
createdAt: Date;
validTime: number;
title: string;
author: string;
image: string;
hastags: Hashtag[];

public static fromEntity(entity: Story): CreateStoryResponseDto {
const dto = new CreateStoryResponseDto();
dto.id = entity.id;
dto.createdAt = entity.createdAt;
dto.validTime = entity.validTime;
dto.title = entity.title;
dto.author = entity.author;
dto.image = entity.image;
dto.hastags = entity.hashtags;
return dto;
}
}
14 changes: 14 additions & 0 deletions src/story/entities/hashtag.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Story } from './story.entity';
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Hashtag {
@PrimaryGeneratedColumn()
id: number;

@Column()
name: string;

@ManyToMany(() => Story, (story) => story.hashtags)
stories: Story[];
}
33 changes: 33 additions & 0 deletions src/story/entities/story.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Hashtag } from './hashtag.entity';
import {
Column,
Entity,
PrimaryGeneratedColumn,
ManyToMany,
JoinTable,
} from 'typeorm';

@Entity()
export class Story {
@PrimaryGeneratedColumn()
id: number;

@Column()
createdAt: Date;

@Column()
validTime: number;

@Column()
title: string;

@Column()
author: string;

@Column()
image: string;

@ManyToMany(() => Hashtag)
@JoinTable()
hashtags: Hashtag[];
}
20 changes: 20 additions & 0 deletions src/story/story.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { StoryController } from './story.controller';
import { StoryService } from './story.service';

describe('StoryController', () => {
let controller: StoryController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [StoryController],
providers: [StoryService],
}).compile();

controller = module.get<StoryController>(StoryController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
27 changes: 27 additions & 0 deletions src/story/story.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Controller, Post, Body, Get } from '@nestjs/common';
import { StoryService } from './story.service';
import { CreateStoryRequestDto } from './dto/create-story-request.dto';
import { PaginationDto } from 'src/page/pagination.dto';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';

@ApiTags('Story')
@Controller('story')
export class StoryController {
constructor(private readonly storyService: StoryService) {}

@Post()
@ApiOperation({ summary: '스토리 생성' })
@ApiResponse({ status: 201, description: '스토리가 생성되었습니다.' })
@ApiResponse({ status: 400, description: '스토리 생성에 실패 하였습니다.' })
async createStory(@Body() dto: CreateStoryRequestDto) {
return await this.storyService.createStory(dto);
}

@Get()
@ApiOperation({ summary: '스토리 조회' })
@ApiResponse({ status: 200, description: '스토리를 조회합니다.' })
@ApiResponse({ status: 400, description: '스토리 조회에 실패하였습니다.' })
async getStory(@Body() dto: PaginationDto) {
return await this.storyService.getStroy(dto);
}
}
12 changes: 12 additions & 0 deletions src/story/story.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Story } from './entities/story.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Module } from '@nestjs/common';
import { StoryService } from './story.service';
import { StoryController } from './story.controller';

@Module({
imports: [TypeOrmModule.forFeature([Story])],
controllers: [StoryController],
providers: [StoryService],
})
export class StoryModule {}
18 changes: 18 additions & 0 deletions src/story/story.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { StoryService } from './story.service';

describe('StoryService', () => {
let service: StoryService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [StoryService],
}).compile();

service = module.get<StoryService>(StoryService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
46 changes: 46 additions & 0 deletions src/story/story.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Story } from './entities/story.entity';
import { Injectable } from '@nestjs/common';
import { CreateStoryRequestDto } from './dto/create-story-request.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateStoryResponseDto } from './dto/create-story-response.dto';
import { PaginationDto } from 'src/page/pagination.dto';
import {
PaginationResult,
createPaginationResult,
} from 'src/page/pagination.util';

@Injectable()
export class StoryService {
constructor(
@InjectRepository(Story)
private storyRepository: Repository<Story>,
) {}

async createStory(
dto: CreateStoryRequestDto,
): Promise<CreateStoryResponseDto> {
const entity = await this.storyRepository.save(dto);
return CreateStoryResponseDto.fromEntity(entity);
}

async getStroy(
dto: PaginationDto,
): Promise<PaginationResult<CreateStoryResponseDto>> {
const [result, total] = await this.storyRepository
.createQueryBuilder('story')
.leftJoinAndSelect('story.hashtags', 'hashtag')
.where(
'story.createdAt > DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL story.validTime HOUR)',
)
.skip(Number((dto.page - 1) * dto.limit))
.take(Number(dto.limit))
.getManyAndCount();

const data = result.map((story) =>
CreateStoryResponseDto.fromEntity(story),
);

return createPaginationResult(data, dto.page, total, dto.limit);
}
}
6 changes: 0 additions & 6 deletions template/environment/prisma/.env.example

This file was deleted.

Loading