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 7 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
23 changes: 23 additions & 0 deletions backend/apps/admin/src/announcement/announcement.resolver.spec.ts
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
})
})
38 changes: 18 additions & 20 deletions backend/apps/admin/src/announcement/announcement.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,42 @@
import { Resolver, Query, Mutation, Args, Int } from '@nestjs/graphql'
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 {
constructor(private readonly announcementService: AnnouncementService) {}

@Mutation(() => Announcement)
createAnnouncement(
async createAnnouncement(
@Args('createAnnouncementInput')
createAnnouncementInput: CreateAnnouncementInput
announcementInput: AnnouncementInput
) {
gyunseo marked this conversation as resolved.
Show resolved Hide resolved
return this.announcementService.create(createAnnouncementInput)
return await this.announcementService.create(announcementInput)
}

@Query(() => [Announcement], { name: 'announcement' })
findAll() {
return this.announcementService.findAll()
@Query(() => [Announcement], { name: 'getAnnouncementsByProblemId' })
async getAnnouncementsByProblemId(
@Args('problemId', { type: () => Int }) problemId: number
) {
return await this.announcementService.findAll(problemId)
}

@Query(() => Announcement, { name: 'announcement' })
findOne(@Args('id', { type: () => Int }) id: number) {
return this.announcementService.findOne(id)
@Query(() => Announcement, { name: 'getAnnouncement' })
async getAnnouncement(@Args('id', { type: () => Int }) id: number) {
return await this.announcementService.findOne(id)
}

@Mutation(() => Announcement)
updateAnnouncement(
@Args('updateAnnouncementInput')
updateAnnouncementInput: UpdateAnnouncementInput
async updateAnnouncement(
@Args('id', { type: () => Int }) id: number,
@Args('announcementInput') announcementInput: AnnouncementInput
) {
return this.announcementService.update(
updateAnnouncementInput.id,
updateAnnouncementInput
)
return await this.announcementService.update(id, announcementInput)
}

@Mutation(() => Announcement)
removeAnnouncement(@Args('id', { type: () => Int }) id: number) {
return this.announcementService.remove(id)
async removeAnnouncement(@Args('id', { type: () => Int }) id: number) {
return await this.announcementService.delete(id)
}
}
138 changes: 138 additions & 0 deletions backend/apps/admin/src/announcement/announcement.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
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 id = faker.number.int()
const announcementInput = {
problemId,
content: faker.string.sample()
}

const announcement = {
...announcementInput,
id
}

const problem = {
problemId
}

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

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

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('create', () => {
it('should return created announcement', async () => {
db.announcement.findFirst.resolves(null)
db.problem.findFirst.resolves(problem)

const res = await service.create(announcementInput)
Lee-won-hyeok marked this conversation as resolved.
Show resolved Hide resolved
expect(res).to.deep.equal(announcement)
})

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

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

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

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

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

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

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

describe('delete', () => {
it('should return deleted announcement', async () => {
db.announcement.delete.resolves(announcement)
const res = await service.delete(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.delete(id)).to.be.rejectedWith(
EntityNotExistException
)
})
})
})
84 changes: 71 additions & 13 deletions backend/apps/admin/src/announcement/announcement.service.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,84 @@
import { Injectable } from '@nestjs/common'
import type { CreateAnnouncementInput } from './dto/create-announcement.input'
import type { UpdateAnnouncementInput } from './dto/update-announcement.input'
import { Injectable, Logger } from '@nestjs/common'
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'
import {
EntityNotExistException,
DuplicateFoundException
} from '@libs/exception'
import { PrismaService } from '@libs/prisma'
import type { AnnouncementInput } from './dto/announcement.input'

@Injectable()
export class AnnouncementService {
create(createAnnouncementInput: CreateAnnouncementInput) {
return createAnnouncementInput
private readonly logger = new Logger(AnnouncementService.name)

constructor(private readonly prisma: PrismaService) {}

async create(
announcementInput: AnnouncementInput
Lee-won-hyeok marked this conversation as resolved.
Show resolved Hide resolved
): Promise<AnnouncementInput> {
const announcement = await this.prisma.announcement.findFirst({
where: {
gyunseo marked this conversation as resolved.
Show resolved Hide resolved
problemId: announcementInput.problemId,
content: announcementInput.content
}
})
if (announcement) throw new DuplicateFoundException('announcement')

const problem = await this.prisma.problem.findFirst({
where: {
Jaehyeon1020 marked this conversation as resolved.
Show resolved Hide resolved
id: announcementInput.problemId
}
})
if (!problem) throw new EntityNotExistException('problem')

return await this.prisma.announcement.create({
data: {
problemId: announcementInput.problemId,
content: announcementInput.content
}
})
}

findAll() {
return `This action returns all announcement`
async findAll(problemId: number) {
return await this.prisma.announcement.findMany({
where: {
problemId
}
})
}

findOne(id: number) {
return `This action returns a #${id} announcement`
async findOne(id: number) {
const announcement = await this.prisma.announcement.findFirst({
where: {
Jaehyeon1020 marked this conversation as resolved.
Show resolved Hide resolved
id
Jaehyeon1020 marked this conversation as resolved.
Show resolved Hide resolved
}
})
if (!announcement) throw new EntityNotExistException('announcement')
return announcement
}

update(id: number, updateAnnouncementInput: UpdateAnnouncementInput) {
return updateAnnouncementInput
async update(id: number, announcementInput: AnnouncementInput) {
try {
return await this.prisma.announcement.update({
where: { id },
data: announcementInput
})
} catch (error) {
if (error instanceof PrismaClientKnownRequestError) {
throw new EntityNotExistException('announcement')
}
}
}

remove(id: number) {
return `This action removes a #${id} announcement`
async delete(id: number) {
try {
return await this.prisma.announcement.delete({
where: { id }
})
} catch (error) {
if (error instanceof PrismaClientKnownRequestError) {
throw new EntityNotExistException('announcement')
}
}
}
}
10 changes: 10 additions & 0 deletions backend/apps/admin/src/announcement/dto/announcement.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { InputType, Int, Field } from '@nestjs/graphql'

@InputType()
export class AnnouncementInput {
@Field(() => Int)
problemId: number

@Field(() => String)
content: string
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
meta {
name: Error: Duplicated Announcement
Copy link
Contributor

Choose a reason for hiding this comment

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

혹시 그 에러 이름 양식 맞춰서 작성해주실 수 있을까요?
UNPROCESSABLE (1) (2) ... (에러 네임) 이렇게 적기로 저번 겨울 방학에 정했는데, 관련 회의록 찾아보고 있으면 드릴게요!

type: graphql
seq: 2
}

post {
url: {{gqlUrl}}
body: graphql
auth: none
}

body:graphql {
mutation {
createAnnouncement(
createAnnouncementInput: {
problemId: 1
content: "Announcement_1_0"
}
) {
id
problemId
content
}
}
}
Loading
Loading