체점은 아마 제대로 되는 거였던거 같다..
하지만 프론트가 안되서 백엔드에서 작업을 시작 후 완료해버렸으므로
이 부분도 메모합니다.
Controller.ts
import {
Get,
Post,
Patch,
Controller,
UsePipes,
ValidationPipe,
Query,
Body,
} from "@nestjs/common"
import { ClassroomService } from "./classroom.service"
@Controller('class/classroom')
@UsePipes(
new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
skipUndefinedProperties: true,
})
)
export class ClassroomController {
constructor(private classroomService: ClassroomService) {}
@Post('/auth')
auth() {
return this.classroomService.setAuth()
}
@Get()
listCourses(@Query('courseId') courseId: string) {
return this.classroomService.listCourses(courseId)
}
@Post('/work')
createCourseWork(@Body() params: any) {
return this.classroomService.createCourseWork(params)
}
@Patch('/work')
listCourseWorkPatch(@Body() params: any) {
return this.classroomService.listCourseWorkPatch(params)
}
@Get('/worklist')
worklist(@Query('courseId') courseId: string, @Query('courseWorkId') courseWorkId: string) {
return this.classroomService.worklist(courseId, courseWorkId)
}
@Post('/logout')
logout() {
return this.classroomService.logout()
}
@Post('/turnin')
turnIn(@Body() params: any) {
return this.classroomService.turnIn(params)
}
}
Service.ts
credentials.json은 Google Cloud Console에서 OAuth 클라이언트를 다운받아서
폴더 최상단(tsconfig.json과 같은 위치)에 위치시키면 된다.
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'
import * as fs from 'fs/promises'
import * as path from 'path'
import * as process from 'process'
import { authenticate } from '@google-cloud/local-auth'
import { classroom_v1, google } from 'googleapis'
@Injectable()
export class ClassroomService {
private readonly TOKEN_PATH = path.join(process.cwd(), 'token.json')
private readonly CREDENTIALS_PATH = path.join(process.cwd(), `credentials${process.env.NODE_ENV === 'production-real' ? '-real' : ''}.json`)
private readonly SCOPES = [
'https://www.googleapis.com/auth/classroom.courses.readonly',
'https://www.googleapis.com/auth/classroom.coursework.students',
'https://www.googleapis.com/auth/classroom.courseworkmaterials',
'https://www.googleapis.com/auth/classroom.coursework.me',
'https://www.googleapis.com/auth/classroom.rosters.readonly',
]
constructor() {}
private auth: any
private classroom: classroom_v1.Classroom
private async loadSaveCredentialsIfExist() {
try {
const content = await fs.readFile(this.TOKEN_PATH, 'utf-8')
const credentials = JSON.parse(content)
return google.auth.fromJSON(credentials)
} catch (err) {
console.log('loadSaveCredentialsIfExist ERORR!! =>', err)
return null
}
}
private async saveCredentials(client) {
const content = await fs.readFile(this.CREDENTIALS_PATH, 'utf-8')
const keys = JSON.parse(content)
const key = keys.installed || keys.web
const payload = JSON.stringify({
type: 'authorized_user',
client_id: key.client_id,
client_secret: key.client_secret,
refresh_token: client.credentials.refresh_token,
})
await fs.writeFile(this.TOKEN_PATH, payload)
}
private async authorize() {
try {
let client: any = await this.loadSaveCredentialsIfExist()
if (client) {
return client
}
client = await authenticate({
scopes: this.SCOPES,
keyfilePath: this.CREDENTIALS_PATH,
})
if (client.credentials) {
await this.saveCredentials(client)
}
return client
} catch (error) {
return error
}
}
/**
* 인증을 위한 api
* @returns
*/
async setAuth() {
try {
if (!this.auth) {
this.auth = await this.authorize()
}
if (!this.classroom) {
this.classroom = google.classroom({ version: 'v1', auth: this.auth })
}
} catch (error) {
throw error
}
}
/**
* 클래스룸 수업 목록
* @param courseId 필수x. 입력시 학생 목록
* @returns
*/
async listCourses(courseId: string) {
try {
await this.setAuth()
let data
if (!courseId) {
const res = await this.classroom.courses.list()
data = res.data.courses
} else {
const res = await this.classroom.courses.students.list({ courseId })
data = res.data.students
}
return {
statusCode: HttpStatus.OK,
message: 'success',
data,
}
} catch (error) {
return {
statusCode: error?.code || HttpStatus.BAD_REQUEST,
message: error?.message || 'error',
error,
}
}
}
/**
* 과제 생성
* @param params.courseId
* @param params.requestBody.state
* @param params.requestBody.title
* @param params.requestBody.description
* @param params.requestBody.workType
* @param params.requestBody.meterials
* @param params.requestBody.dueDate
* @param params.requestBody.dueTime
* @param params.requestBody.maxPoints
*/
async createCourseWork(params: any) {
try {
await this.setAuth()
const res = await this.classroom.courses.courseWork.create({
...params,
})
const courseWorkId = res.data.id
const res2 = await this.classroom.courses.courseWork.studentSubmissions.list({
courseId: params.courseId,
courseWorkId,
})
const courseWorkSubmissions = res2.data.studentSubmissions
return {
statusCode: HttpStatus.OK,
message: 'success',
data: {
courseId: params.courseId,
courseWorkId,
courseWorkSubmissions,
},
}
} catch (error) {
return {
statusCode: error?.code || HttpStatus.BAD_REQUEST,
message: error?.message || 'error',
error,
}
}
}
/**
* 과제 채점
* @param params.courseId
* @param params.courseWorkId
* @param params.studentId
* @param params.grade
*/
async listCourseWorkPatch(params: any) {
try {
await this.setAuth()
const res = await this.classroom.courses.courseWork.studentSubmissions.patch({
courseId: params.courseId,
courseWorkId: params.courseWorkId,
id: params.studentId,
updateMask: 'draftGrade,assignedGrade',
requestBody: {
draftGrade: params.grade,
assignedGrade: params.grade,
},
})
return {
statusCode: HttpStatus.OK,
message: 'success',
data: res.data,
}
} catch (err) {
console.log('tlqkf ERROR', err)
return {
statusCode: err?.code || HttpStatus.BAD_REQUEST,
message: 'error',
errors: err.errors,
err,
}
}
}
/**
* 로그아웃
* @returns
*/
async logout() {
try {
await fs.unlink(this.TOKEN_PATH)
this.auth = null
this.classroom = null
return {
statusCode: HttpStatus.OK,
message: '로그아웃 되었습니다.',
}
} catch (err) {
return {
statusCode: err?.code || HttpStatus.BAD_REQUEST,
message: '로그아웃 실패',
error: err,
}
}
}
/**
* 과제 제출 (학생)
* @param params.courseId
* @param params.courseWorkId
* @param params.studentId
*/
async turnIn(params: any) {
try {
await this.setAuth()
const response = await this.classroom.courses.courseWork.studentSubmissions.turnIn({
courseId: params.courseId,
courseWorkId: params.courseWorkId,
id: params.studentId,
})
console.log('Assignment turned in:', response.data)
return {
statusCode: HttpStatus.OK,
message: 'success',
data: response.data,
}
} catch (error) {
console.error('Error turning in assignment:', error);
return {
statusCode: error?.code || HttpStatus.BAD_REQUEST,
message: 'error',
error,
}
}
}
/**
* 과제/제출 목록
* @param courseId // 클래스룸 아이디
* @param courseWorkId // 과제 아이디. 없을 경우 과제 목록, 있을 경우 제출 목록
*/
async worklist(courseId: string, courseWorkId: string) {
if (!courseId) {
throw new HttpException('Valid courseId needed.', HttpStatus.BAD_REQUEST)
}
try {
await this.setAuth()
let res
if (courseWorkId) {
res = await this.classroom.courses.courseWork.studentSubmissions.list({
courseId,
courseWorkId,
})
} else {
res = await this.classroom.courses.courseWork.list({
courseId,
})
}
return {
statusCode: HttpStatus.OK,
message: 'success',
data: res.data,
}
} catch (err) {
console.log('qudtls ERROR', err)
return {
statusCode: err?.code || HttpStatus.BAD_REQUEST,
message: 'error',
errors: err.errors,
err,
}
}
}
}
참고로 studentSubmissions.patch에서 studentId는 사용자ID가 아니라 과제별 개별 아이디가 존재했다..
왜 학생 아이디라고 설명해놔서 나를 헤매게 만들었는가..
(제출 목록에서 id가 넣어야할 값이다.. userId(학생ID)가 아니라)
반응형
'JavaScript > Nest' 카테고리의 다른 글
NestJS aws 메일 발송하기(feat. mailer, ejs) (0) | 2023.02.03 |
---|---|
NestJS 핵심 도메인 로직을 포함하는 프로바이더(Provider) (0) | 2022.08.23 |
NestJS 유저 서비스의 인터페이스 (1) | 2022.08.22 |
NestJS 컨트롤러(Controller) [2] (0) | 2022.08.22 |
NestJS 컨트롤러(Controller) [1] (0) | 2022.08.17 |