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

feat(be): implement admin announcement module #1270

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2897d81
feat(be): implement admin annoouncement create and read
Lee-won-hyeok Jan 22, 2024
10b70ef
feat(be): implement admin annoouncement create and read
Lee-won-hyeok Jan 22, 2024
a1e231f
feat(be): implement admin annoouncement create and read
Lee-won-hyeok Jan 22, 2024
77fd160
Merge branch 'main' into 1047-implement-admin-announcement-module
Lee-won-hyeok Jan 25, 2024
4d2cc7b
feat: implement admin announcement update and delete
Lee-won-hyeok Jan 25, 2024
485b8a2
feat: add test code and additional error handler
Lee-won-hyeok Jan 29, 2024
2b2cbb6
docs: add bruno announcement requests
Lee-won-hyeok Jan 29, 2024
997c8a6
Merge branch 'main' into 1047-implement-admin-announcement-module
Lee-won-hyeok Feb 13, 2024
06be8aa
feat: change business exception handling
Lee-won-hyeok Feb 13, 2024
1f7fb66
Merge branch 'main' into 1047-implement-admin-announcement-module
Lee-won-hyeok Feb 13, 2024
7cd01ee
chore: change business exception handling
Lee-won-hyeok Feb 13, 2024
6c1cf38
docs: bruno collection docs
Lee-won-hyeok Feb 13, 2024
1ed30ac
chore: change prisma exception handler
Lee-won-hyeok Feb 13, 2024
966f437
fix: update test code
Lee-won-hyeok Feb 13, 2024
8f81940
feat: match service and resolver function name
Lee-won-hyeok Feb 15, 2024
95a6e4c
chore: fix test code
Lee-won-hyeok Feb 15, 2024
63ad162
chore: fix ts issue
Lee-won-hyeok Feb 15, 2024
eb22bb7
Merge branch 'main' into 1047-implement-admin-announcement-module
Lee-won-hyeok Feb 15, 2024
2c4a950
feat: fix create & update logic
Lee-won-hyeok Feb 19, 2024
710f50c
docs: submission bruno docs & rename API
Lee-won-hyeok Feb 19, 2024
92f0c01
Merge branch 'main' into 1047-implement-admin-announcement-module
Lee-won-hyeok Feb 22, 2024
dacd6cf
feat: update announcement test code
Lee-won-hyeok Feb 22, 2024
961ac1d
docs: update bruno
Lee-won-hyeok Feb 22, 2024
c510531
Merge branch 'main' into 1047-implement-admin-announcement-module
Lee-won-hyeok Feb 29, 2024
79a97dd
docs: bruno assert
Lee-won-hyeok Feb 29, 2024
0c96747
docs: write bruno docs
Lee-won-hyeok Feb 29, 2024
65076cf
Merge branch 'main' into 1047-implement-admin-announcement-module
Lee-won-hyeok Apr 1, 2024
e9d485f
chore: remove unused dto
Lee-won-hyeok Apr 1, 2024
826f129
docs: update bruno docs
Lee-won-hyeok Apr 1, 2024
b96cc1d
Merge branch 'main' into 1047-implement-admin-announcement-module
Lee-won-hyeok Apr 1, 2024
2aadb11
Merge branch 'main' into 1047-implement-admin-announcement-module
Lee-won-hyeok Apr 29, 2024
81c2f17
docs: update bruno docs
Lee-won-hyeok Apr 29, 2024
5d5a0a7
Merge branch '1047-implement-admin-announcement-module' of https://gi…
Lee-won-hyeok Apr 29, 2024
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,23 @@
import { Test, type TestingModule } from '@nestjs/testing'
import { expect } from 'chai'
import { AnnouncementResolver } from './announcement.resolver'
import { AnnouncementService } from './announcement.service'

describe('AnnouncementResolver', () => {
let resolver: AnnouncementResolver

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AnnouncementResolver,
{ provide: AnnouncementService, useValue: {} }
]
}).compile()

resolver = module.get<AnnouncementResolver>(AnnouncementResolver)
})

it('shoulld be defined', () => {
expect(resolver).to.be.ok
})
})
89 changes: 68 additions & 21 deletions apps/backend/apps/admin/src/announcement/announcement.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,91 @@
import { InternalServerErrorException, Logger } from '@nestjs/common'
import { Resolver, Query, Mutation, Args, Int } from '@nestjs/graphql'
import { Announcement } from '@generated'
import {
DuplicateFoundException,
EntityNotExistException
} from '@libs/exception'
import { Announcement } from '@admin/@generated'
import { AnnouncementService } from './announcement.service'
import { CreateAnnouncementInput } from './dto/create-announcement.input'
import { UpdateAnnouncementInput } from './dto/update-announcement.input'
import { AnnouncementInput } from './dto/announcement.input'

@Resolver(() => Announcement)
export class AnnouncementResolver {
private readonly logger = new Logger(AnnouncementResolver.name)
constructor(private readonly announcementService: AnnouncementService) {}

@Mutation(() => Announcement)
createAnnouncement(
async createAnnouncement(
@Args('createAnnouncementInput')
createAnnouncementInput: CreateAnnouncementInput
announcementInput: AnnouncementInput
) {
return this.announcementService.create(createAnnouncementInput)
try {
return await this.announcementService.createAnnouncement(
announcementInput
)
} catch (error) {
if (error instanceof DuplicateFoundException || EntityNotExistException) {
throw error.convert2HTTPException()
}
this.logger.error(error)
throw new InternalServerErrorException()
}
}

@Query(() => [Announcement], { name: 'announcement' })
findAll() {
return this.announcementService.findAll()
@Query(() => [Announcement], { name: 'getAnnouncements' })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 Query name을 따로 'getAnnouncements'로 지정하지 않아도 밑에 메소드 이름으로 적용될거에요!

async getAnnouncements(
@Args('contestId', { type: () => Int }) contestId: number,
@Args('problemId', { type: () => Int, nullable: true }) problemId?: number
) {
try {
return await this.announcementService.getAnnouncements(
contestId,
problemId
)
} catch (error) {
this.logger.error(error)
throw new InternalServerErrorException()
}
}

@Query(() => Announcement, { name: 'announcement' })
findOne(@Args('id', { type: () => Int }) id: number) {
return this.announcementService.findOne(id)
@Query(() => Announcement, { name: 'getAnnouncementById' })
async getAnnouncementById(@Args('id', { type: () => Int }) id: number) {
try {
return await this.announcementService.getAnnouncementById(id)
} catch (error) {
if (error instanceof EntityNotExistException) {
throw error.convert2HTTPException()
}
this.logger.error(error)
throw new InternalServerErrorException()
}
}

@Mutation(() => Announcement)
updateAnnouncement(
@Args('updateAnnouncementInput')
updateAnnouncementInput: UpdateAnnouncementInput
async updateAnnouncement(
@Args('id', { type: () => Int }) id: number,
@Args('content', { type: () => String }) content: string
) {
return this.announcementService.update(
updateAnnouncementInput.id,
updateAnnouncementInput
)
try {
return await this.announcementService.updateAnnouncement(id, content)
} catch (error) {
if (error instanceof EntityNotExistException) {
throw error.convert2HTTPException()
}
this.logger.error(error)
throw new InternalServerErrorException()
}
}

@Mutation(() => Announcement)
removeAnnouncement(@Args('id', { type: () => Int }) id: number) {
return this.announcementService.remove(id)
async removeAnnouncement(@Args('id', { type: () => Int }) id: number) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

진짜진짜 사소한건데... CRUD니까 remove대신 delete 어떨까요 ㅎㅎ..

try {
return await this.announcementService.removeAnnouncement(id)
} catch (error) {
if (error instanceof EntityNotExistException) {
throw error.convert2HTTPException()
}
this.logger.error(error)
throw new InternalServerErrorException()
}
}
}
147 changes: 147 additions & 0 deletions apps/backend/apps/admin/src/announcement/announcement.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { Test, type TestingModule } from '@nestjs/testing'
import { faker } from '@faker-js/faker'
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'
import { expect } from 'chai'
import { stub } from 'sinon'
import {
DuplicateFoundException,
EntityNotExistException
} from '@libs/exception'
import { PrismaService } from '@libs/prisma'
import { AnnouncementService } from './announcement.service'

const problemId = faker.number.int()
const contestId = faker.number.int()
const id = faker.number.int()
const announcementInput = {
problemId,
contestId,
content: faker.string.sample()
}

const announcement = {
...announcementInput,
id
}

const contestProblem = {
problemId
}

const db = {
announcement: {
findFirst: stub(),
findFirstOrThrow: stub(),
findMany: stub().resolves([announcement]),
update: stub(),
delete: stub(),
create: stub().resolves(announcement)
},
contestProblem: {
findFirst: stub(),
findFirstOrThrow: stub()
}
}

const PrismaErrorInstance = new PrismaClientKnownRequestError('error', {
code: '4xx',
clientVersion: 'x.x.x'
})

PrismaErrorInstance.name = 'NotFoundError'

describe('AnnouncementService', () => {
let service: AnnouncementService

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AnnouncementService, { provide: PrismaService, useValue: db }]
}).compile()

service = module.get<AnnouncementService>(AnnouncementService)
})

it('should be defined', () => {
expect(service).to.be.ok
})

describe('createAnnouncement', () => {
it('should return created announcement', async () => {
db.announcement.findFirst.resolves(null)
db.contestProblem.findFirstOrThrow.resolves(contestProblem)

const res = await service.createAnnouncement(announcementInput)
expect(res).to.deep.equal(announcement)
})

it('should throw error when given announcement already exists', async () => {
db.announcement.findFirst.resolves(announcement)

await expect(
service.createAnnouncement(announcementInput)
).to.be.rejectedWith(DuplicateFoundException)
})

it('should throw error when given problemId is invalid', async () => {
db.announcement.findFirst.resolves(null)
db.contestProblem.findFirstOrThrow.rejects(PrismaErrorInstance)

await expect(
service.createAnnouncement(announcementInput)
).to.be.rejectedWith(EntityNotExistException)
})
})

describe('getAnnouncements', () => {
it('should return all announcements', async () => {
db.announcement.findMany()
const res = await service.getAnnouncements(problemId)
expect(res).to.deep.equal([announcement])
})
})

describe('getAnnouncementById', () => {
it('should throw error when given id is invalid', async () => {
db.announcement.findFirstOrThrow.rejects(PrismaErrorInstance)
await expect(service.getAnnouncementById(id)).to.be.rejectedWith(
EntityNotExistException
)
})
it('should return an announcement', async () => {
db.announcement.findFirstOrThrow.resolves(announcement)
const res = await service.getAnnouncementById(id)
expect(res).to.deep.equal(announcement)
})
})

describe('updateAnnouncement', () => {
it('should return updated announcement', async () => {
db.announcement.update.resolves(announcement)
const res = await service.updateAnnouncement(
id,
announcementInput.content
)
expect(res).to.deep.equal(announcement)
})
it('should throw error when given id is invalid', async () => {
db.announcement.update.rejects(PrismaErrorInstance)
await expect(
service.updateAnnouncement(id, announcementInput.content)
).to.be.rejectedWith(EntityNotExistException)
})
})

describe('removeAnnouncement', () => {
it('should return deleted announcement', async () => {
db.announcement.delete.resolves(announcement)
const res = await service.removeAnnouncement(id)
expect(res).to.deep.equal(announcement)
})
it('should throw error when given id is invalid', async () => {
db.announcement.delete.rejects(PrismaErrorInstance)
await expect(service.removeAnnouncement(id)).to.be.rejectedWith(
EntityNotExistException
)
})
})
})
Loading
Loading