This commit is contained in:
2023-11-23 00:30:50 +09:00
parent e8ddadfd0a
commit d354dbb27f
18 changed files with 1907 additions and 817 deletions

View File

@ -34,9 +34,7 @@ func NewAuthController(service services.AuthService, tokenService services.Token
// @Accept json
// @Produce json
//
// @Param username body string true "username"
// @Param name body string true "이름"
// @Param password body string true "비밀번호"
// @Param registerBody body models.RegisterRequest true "Register Body"
//
// @Success 200 {object} models.RegisterResponse
// @Router /auth/register [post]
@ -69,8 +67,7 @@ func (controller *authController) Register(c *gin.Context) {
// @Accept json
// @Produce json
//
// @Param username body string true "username"
// @Param password body string true "비밀번호"
// @Param loginBody body models.LoginRequest true "Login Body"
//
// @Success 200 {object} models.LoginResponse
// @Router /auth/login [post]

View File

@ -0,0 +1,306 @@
package controllers
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"learnsteam/learsteam-quiz-api/internal/models"
"learnsteam/learsteam-quiz-api/internal/services"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type ExamController interface {
List(*gin.Context)
Find(*gin.Context)
Create(*gin.Context)
Update(*gin.Context)
Patch(*gin.Context)
}
type examController struct {
service services.ExamService
userService services.UserService
programService services.ProgramService
}
func NewExamController(service services.ExamService, userService services.UserService, programService services.ProgramService) ExamController {
return &examController{
service: service,
userService: userService,
programService: programService,
}
}
// Exam List
//
// @Summary 응시 목록 가져오기
// @Description 응시 목록을 가져옵니다.
// @Tags Exam
//
// @Accept json
// @Produce json
//
// @Param q query string false "검색어"
// @Param page query int false "페이지"
// @Param limit query int false "페이지 사이즈"
//
// @Success 200 {object} models.ExamListResponse
// @Router /exam [get]
func (controller *examController) List(c *gin.Context) {
q := c.DefaultQuery("q", "")
page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
if err != nil {
fmt.Println("error : page")
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
limit, err := strconv.Atoi(c.DefaultQuery("limit", "10"))
if err != nil {
fmt.Println("error : limit")
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
result, err := controller.service.List(q, page, limit)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
totalNumber, err := controller.service.Total(q)
if err != nil {
fmt.Println("error : list")
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
totalPage := int64(totalNumber/int64(limit) + 1)
response := models.ExamListResponse{
Data: *result,
Total: totalNumber,
Page: page,
TotalPage: totalPage,
PageSize: limit,
}
c.JSON(http.StatusOK, response)
}
// Get exam
//
// @Summary 응시(시험) 정보 가져오기
// @Description ID로 응시 정보를 가져옵니다.
// @Tags Exam
//
// @Accept json
// @Produce json
//
// @Param id path string true "응시 ID"
//
// @Success 200 {object} models.ExamResponse
// @Router /exam/{id} [get]
func (controller *examController) Find(c *gin.Context) {
id := c.Param("id")
result, err := controller.service.Find(id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, result)
}
// Create Exam
//
// @Summary 응시 매칭 생성
// @Description 응시 매칭을 만듭니다.
// @Tags Exam
//
// @Accept json
// @Produce json
//
// @Param examBody body models.ExamRequest true "Exam Body"
//
// @Success 200 {object} models.ExamResponse
// @Router /exam [post]
func (controller *examController) Create(c *gin.Context) {
var request models.ExamRequest
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fmt.Println("users", request.Users)
program, err := controller.programService.Find(request.ProgramID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var users []string
err = json.Unmarshal(request.Users, &users)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
count := 0
for _, user_id := range users {
user, err := controller.userService.Find(user_id)
if err != nil {
continue
}
exam := &models.Exam{
ID: uuid.NewString(),
ProgramID: program.ID,
UserID: user_id,
Subject: program.Subject,
Program: program.Course,
Name: user.Name,
Score: 0,
Total: 5,
Status: "ready",
}
_, err = controller.service.Create(exam)
if err != nil {
continue
}
count += 1
}
c.JSON(http.StatusOK, gin.H{"count": count})
}
// Update Exam
//
// @Summary 퀴즈 프로그램 수정
// @Description 퀴즈 프로그램을 수정합니다.
// @Tags Exam
//
// @Accept json
// @Produce json
//
// @Param examUpdateBody body models.ExamUpdateRequest true "Exam Update Body"
//
// @Success 200 {object} models.ExamResponse
// @Router /exam [put]
func (controller *examController) Update(c *gin.Context) {
id := c.Param("id")
var request models.ExamUpdateRequest
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fmt.Println("request", request)
exam, err := controller.service.Find(id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
exam.ProgramID = request.ProgramID
exam.UserID = request.UserID
exam.Subject = request.Subject
exam.Program = request.Program
exam.Name = request.Name
exam.Status = request.Status
exam.Score = request.Score
exam.Total = request.Total
if request.StartAt != "" {
fmt.Println("request.StartAt", request.StartAt)
start_at, err := time.Parse("2006-01-02T15:04:05-07:00", request.StartAt)
if err == nil {
exam.StartAt = &start_at
}
fmt.Println("start_at", start_at)
}
if request.EndAt != "" {
fmt.Println("request.EndAt", request.EndAt)
end_at, err := time.Parse("2006-01-02T15:04:05-07:00", request.EndAt)
if err == nil {
exam.EndAt = &end_at
}
fmt.Println("end_at", end_at)
}
fmt.Println("exam", exam)
result, err := controller.service.Update(exam)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fmt.Println("result", result)
c.JSON(http.StatusOK, result)
}
// Patch Exam
//
// @Summary 퀴즈 프로그램 정보 변경
// @Description 퀴즈 프로그램 정보를 변경합니다.
// @Tags Exam
//
// @Accept json
// @Produce json
//
// @Param examPatchBody body models.ExamPatchRequest true "Exam Patch Body (변경할 필드만 입력)"
//
// @Success 200 {object} models.ExamResponse
// @Router /exam [patch]
func (controller *examController) Patch(c *gin.Context) {
id := c.Param("id")
var request models.ExamPatchRequest
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fmt.Println("request", request)
exam, err := controller.service.Find(id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if request.Status != "" {
exam.Status = request.Status
}
if request.StartAt != "" {
fmt.Println("request.StartAt", request.StartAt)
start_at, err := time.Parse("2006-01-02T15:04:05-07:00", request.StartAt)
if err == nil {
exam.StartAt = &start_at
}
fmt.Println("start_at", start_at)
}
if request.EndAt != "" {
fmt.Println("request.EndAt", request.EndAt)
end_at, err := time.Parse("2006-01-02T15:04:05-07:00", request.EndAt)
if err == nil {
exam.EndAt = &end_at
}
fmt.Println("end_at", end_at)
}
fmt.Println("exam", exam)
result, err := controller.service.Update(exam)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fmt.Println("result", result)
c.JSON(http.StatusOK, result)
}

View File

@ -121,12 +121,7 @@ func (controller *programController) Find(c *gin.Context) {
// @Accept json
// @Produce json
//
// @Param subject body string true "프로그램 제목"
// @Param course body string true "프로그램 코스"
// @Param content body string true "프로그램 내용"
// @Param tag body []string true "프로그램 태그"
// @Param status body string true "프로그램 상태 on 또는 off"
// @Param publish_at body string true "프로그램 발행 날짜"
// @Param programBody body models.ProgramRequest true "Program Body"
//
// @Success 200 {object} models.ProgramResponse
// @Router /program [post]
@ -174,12 +169,7 @@ func (controller *programController) Create(c *gin.Context) {
// @Accept json
// @Produce json
//
// @Param subject body string true "프로그램 제목"
// @Param course body string true "프로그램 코스"
// @Param content body string true "프로그램 내용"
// @Param tag body []string true "프로그램 태그"
// @Param status body string true "프로그램 상태 on 또는 off"
// @Param publish_at body string true "프로그램 발행 날짜"
// @Param programUpdateBody body models.ProgramRequest true "Program Body"
//
// @Success 200 {object} models.ProgramResponse
// @Router /program [put]

View File

@ -112,14 +112,7 @@ func (controller *quizController) Find(c *gin.Context) {
// @Accept json
// @Produce json
//
// @Param program_id body string true "프로그램 ID"
// @Param sequence body int true "퀴즈 순서"
// @Param quiz_type body string true "퀴즈 타입 : choice, check, ox, input"
// @Param question body string true "퀴즈 문제"
// @Param choice body []string true "퀴즈 선택지 : ['선택1','선택2','선택3', '선택4']"
// @Param answer body []string true "퀴즈 정답 : [1,3]"
// @Param hint body string true "퀴즈 힌트"
// @Param comment body string true "퀴즈 해설"
// @Param quizBody body models.QuizRequest true "Quiz Body"
//
// @Success 200 {object} models.QuizResponse
// @Router /quiz [post]
@ -162,15 +155,7 @@ func (controller *quizController) Create(c *gin.Context) {
// @Accept json
// @Produce json
//
// @Param id path string true "퀴즈 ID"
// @Param program_id body string true "프로그램 ID"
// @Param sequence body int true "퀴즈 순서"
// @Param quiz_type body string true "퀴즈 타입 : choice, check, ox, input"
// @Param question body string true "퀴즈 문제"
// @Param choice body []string true "퀴즈 선택지 : ['선택1','선택2','선택3', '선택4']"
// @Param answer body []string true "퀴즈 정답 : [1,3]"
// @Param hint body string true "퀴즈 힌트"
// @Param comment body string true "퀴즈 해설"
// @Param quizBody body models.QuizRequest true "Quiz Body"
//
// @Success 200 {object} models.QuizResponse
// @Router /quiz [put]

View File

@ -40,5 +40,6 @@ func AutoMigrate() {
&models.Token{},
&models.Program{},
&models.Quiz{},
&models.Exam{},
)
}

70
internal/models/exam.go Normal file
View File

@ -0,0 +1,70 @@
package models
import (
"time"
"gorm.io/datatypes"
)
type Exam struct {
ID string `json:"id" db:"id" example:"ef74c59a-c707-4162-a52b-455906c81ec1" gorm:"column:id;size:255;primary_key;"`
ProgramID string `json:"program_id" db:"program_id" example:"7f9329f5-2e36-4638-92d2-73064b7291a4" gorm:"column:program_id;size:255;uniqueIndex:idx_exam;"`
UserID string `json:"user_id" db:"user_id" example:"f95159dd-c42c-490d-ac6b-ca5d88a266bb" gorm:"column:user_id;size:255;uniqueIndex:idx_exam;"`
Subject string `json:"subject" db:"subject" example:"출력 Print" gorm:"column:subject;size:255;index;"`
Program string `json:"program" db:"program" example:"파이썬 초급 과정" gorm:"column:program;size:255;index;"`
Name string `json:"name" db:"name" example:"홍길순" gorm:"column:name;size:255;index;"`
Score int `json:"score" db:"score" example:"5" gorm:"column:score;"`
Total int `json:"total" db:"total" example:"5" gorm:"column:total;"`
Status string `json:"status" example:"ready" gorm:"column:status;size:10;index;"`
StartAt *time.Time `json:"start_at" gorm:"column:start_at;index;"`
EndAt *time.Time `json:"end_at" gorm:"column:end_at;index;"`
UpdatedAt time.Time `json:"-" gorm:"column:updated_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;index;->:false"`
CreatedAt time.Time `json:"-" gorm:"column:created_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index;->:false;<-:create"`
}
type ExamRequest struct {
ProgramID string `json:"program_id" example:"7f9329f5-2e36-4638-92d2-73064b7291a4"`
Users datatypes.JSON `json:"users"`
}
type ExamResponse struct {
ID string `json:"id" db:"id" example:"ef74c59a-c707-4162-a52b-455906c81ec1"`
ProgramID string `json:"program_id" example:"7f9329f5-2e36-4638-92d2-73064b7291a4"`
UserID string `json:"user_id" example:"f95159dd-c42c-490d-ac6b-ca5d88a266bb"`
Subject string `json:"subject" example:"출력 Print"`
Program string `json:"program" example:"파이썬 초급 과정"`
Name string `json:"name" example:"홍길순"`
Score int `json:"score" example:"5"`
Total int `json:"total" example:"5"`
Status string `json:"status" example:"ready"`
StartAt string `json:"start_at,omitempty" example:"2023-11-10T13:10:00+09:00"`
EndAt string `json:"end_at,omitempty" example:"2023-11-10T13:25:00+09:00"`
}
type ExamUpdateRequest struct {
ProgramID string `json:"program_id" example:"7f9329f5-2e36-4638-92d2-73064b7291a4"`
UserID string `json:"user_id" example:"f95159dd-c42c-490d-ac6b-ca5d88a266bb"`
Subject string `json:"subject" example:"출력 Print"`
Program string `json:"program" example:"파이썬 초급 과정"`
Name string `json:"name" example:"홍길순"`
Score int `json:"score" example:"5"`
Total int `json:"total" example:"5"`
Status string `json:"status" example:"ready"`
StartAt string `json:"start_at,omitempty" example:"2023-11-10T13:10:00+09:00"`
EndAt string `json:"end_at,omitempty" example:"2023-11-10T13:25:00+09:00"`
}
type ExamPatchRequest struct {
Score int `json:"score,omitempty" example:"4"`
Status string `json:"status,omitempty" example:"rating"`
StartAt string `json:"start_at,omitempty" example:"2023-11-10T13:10:00+09:00"`
EndAt string `json:"end_at,omitempty" example:"2023-11-10T13:25:00+09:00"`
}
type ExamListResponse struct {
Data []Exam `json:"data"`
Total int64 `json:"total" example:"999"`
Page int `json:"page" example:"1"`
TotalPage int64 `json:"totalPage" example:"99"`
PageSize int `json:"pageSize" example:"10"`
}

View File

@ -15,8 +15,8 @@ type Program struct {
Tags string `json:"-" db:"tags" gorm:"column:tags;index;"`
Status string `json:"status" example:"on" gorm:"column:status;size:10;index;"`
PublishAt time.Time `json:"publish_at" example:"2023-11-10T00:00:00+09:00" gorm:"column:publish_at;index;"`
UpdatedAt time.Time `json:"-" gorm:"column:updated_at;index;"`
CreatedAt time.Time `json:"-" gorm:"column:created_at;index;"`
UpdatedAt time.Time `json:"-" gorm:"column:updated_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;index;->:false"`
CreatedAt time.Time `json:"-" gorm:"column:created_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index;->:false;<-:create"`
}
type ProgramRequest struct {

View File

@ -16,8 +16,8 @@ type Quiz struct {
Answer datatypes.JSON `json:"answer" db:"answer" gorm:"column:answer;"`
Hint string `json:"hint" db:"hint" example:"퀴즈 힌트" gorm:"column:answer;size:1024;"`
Comment string `json:"comment" db:"comment" example:"퀴즈 해설" gorm:"column:comment;size:1024;"`
UpdatedAt time.Time `json:"-" gorm:"column:updated_at;index;"`
CreatedAt time.Time `json:"-" gorm:"column:created_at;index;"`
UpdatedAt time.Time `json:"-" gorm:"column:updated_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;index;->:false"`
CreatedAt time.Time `json:"-" gorm:"column:created_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index;->:false;<-:create"`
}
type QuizRequest struct {

View File

@ -8,8 +8,8 @@ type User struct {
Name string `json:"name" db:"name" example:"홍길동" gorm:"column:name;size:50;"`
Score int32 `json:"score" db:"score" example:"9999" gorm:"column:score;"`
Password string `json:"-" db:"password" gorm:"column:password;size:255;not null;"`
UpdatedAt time.Time `json:"-" gorm:"column:updated_at;index;"`
CreatedAt time.Time `json:"-" gorm:"column:created_at;index;"`
UpdatedAt time.Time `json:"-" gorm:"column:updated_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;index;->:false"`
CreatedAt time.Time `json:"-" gorm:"column:created_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index;->:false;<-:create"`
}
type UserListResponse struct {

View File

@ -0,0 +1,81 @@
package repositories
import (
"learnsteam/learsteam-quiz-api/internal/models"
"gorm.io/gorm"
)
type examRepository struct {
DB *gorm.DB
}
func NewExamRepository(db *gorm.DB) ExamRepository {
return &examRepository{
DB: db,
}
}
type ExamRepository interface {
List(string, int, int) (*[]models.Exam, error)
Total(string) (int64, error)
Find(string) (*models.Exam, error)
Create(*models.Exam) (*models.Exam, error)
Update(*models.Exam) (*models.Exam, error)
Delete(string) error
}
func (r *examRepository) List(q string, page int, limit int) (*[]models.Exam, error) {
var exams *[]models.Exam
var err error
offset := limit * (page - 1)
if q != "" {
err = r.DB.Offset(offset).Limit(limit).Order("created_at DESC").Where("subject LIKE ? OR program LIKE ? OR name LIKE ?", "%"+q+"%", "%"+q+"%", "%"+q+"%").Find(&exams).Error
} else {
err = r.DB.Offset(offset).Limit(limit).Order("created_at DESC").Find(&exams).Error
}
return exams, err
}
func (r *examRepository) Total(q string) (int64, error) {
var total int64
var err error
if q != "" {
err = r.DB.Model(&models.Exam{}).Where("subject LIKE ? OR program LIKE ? OR name LIKE ?", "%"+q+"%", "%"+q+"%", "%"+q+"%").Count(&total).Error
} else {
err = r.DB.Model(&models.Exam{}).Count(&total).Error
}
return total, err
}
func (r *examRepository) Find(id string) (*models.Exam, error) {
var exam *models.Exam
err := r.DB.Where("id = ?", id).First(&exam).Error
return exam, err
}
func (r *examRepository) Create(exam *models.Exam) (*models.Exam, error) {
err := r.DB.Create(&exam).Error
return exam, err
}
func (r *examRepository) Update(exam *models.Exam) (*models.Exam, error) {
var row *models.Exam
if err := r.DB.Where("id=?", exam.ID).First(&row).Error; err != nil {
return nil, err
}
err := r.DB.Model(&row).Select("*").Updates(&exam).Error
return row, err
}
func (r *examRepository) Delete(id string) error {
var exam *models.Exam
if err := r.DB.Where("id=?", id).First(&exam).Error; err != nil {
return err
}
err := r.DB.Delete(&exam).Error
return err
}

65
internal/routers/exam.go Normal file
View File

@ -0,0 +1,65 @@
package routers
import (
"learnsteam/learsteam-quiz-api/internal/controllers"
"learnsteam/learsteam-quiz-api/internal/middleware"
"learnsteam/learsteam-quiz-api/internal/repositories"
"learnsteam/learsteam-quiz-api/internal/services"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
func InitExamRouter(db *gorm.DB, router *gin.Engine) {
r := NewExamRouter(db, router)
r.SetExamRouter(db, router)
}
type ExamRouter interface {
SetRouter(db *gorm.DB, router *gin.Engine)
}
type examRouter struct {
db *gorm.DB
repository repositories.ExamRepository
programRepository repositories.ProgramRepository
userRepository repositories.UserRepository
service services.ExamService
programService services.ProgramService
userService services.UserService
controller controllers.ExamController
router *gin.Engine
}
func NewExamRouter(db *gorm.DB, router *gin.Engine) *examRouter {
repository := repositories.NewExamRepository(db)
programRepository := repositories.NewProgramRepository(db)
tokenRepository := repositories.NewTokenRepository(db)
userRepository := repositories.NewUserRepository(db)
service := services.NewExamService(repository)
programService := services.NewProgramService(programRepository)
userService := services.NewUserService(userRepository, tokenRepository)
controller := controllers.NewExamController(service, userService, programService)
return &examRouter{
db: db,
repository: repository,
service: service,
controller: controller,
router: router,
}
}
func (r *examRouter) SetExamRouter(db *gorm.DB, router *gin.Engine) {
group := router.Group("/exam")
group.GET("", middleware.Auth(), r.controller.List)
group.GET("/:id", middleware.Auth(), r.controller.Find)
group.POST("", middleware.Auth(), r.controller.Create)
group.PUT("/:id", middleware.Auth(), r.controller.Update)
group.PATCH("/:id", middleware.Auth(), r.controller.Patch)
}

View File

@ -25,6 +25,7 @@ func Init() {
InitUserRouter(maindb, Router)
InitProgramRouter(maindb, Router)
InitQuizRouter(maindb, Router)
InitExamRouter(maindb, Router)
InitSwaggerRouter(Router)
}

56
internal/services/exam.go Normal file
View File

@ -0,0 +1,56 @@
package services
import (
"learnsteam/learsteam-quiz-api/internal/models"
"learnsteam/learsteam-quiz-api/internal/repositories"
)
type examService struct {
repository repositories.ExamRepository
}
type ExamService interface {
List(string, int, int) (*[]models.Exam, error)
Total(string) (int64, error)
Find(string) (*models.Exam, error)
Create(*models.Exam) (*models.Exam, error)
Update(*models.Exam) (*models.Exam, error)
Delete(string) error
}
func NewExamService(repository repositories.ExamRepository) ExamService {
return &examService{
repository: repository,
}
}
func (s *examService) List(q string, page int, limit int) (*[]models.Exam, error) {
return s.repository.List(q, page, limit)
}
func (s *examService) Total(q string) (int64, error) {
return s.repository.Total(q)
}
func (s *examService) Find(id string) (*models.Exam, error) {
return s.repository.Find(id)
}
func (s *examService) Create(exam *models.Exam) (*models.Exam, error) {
result, err := s.repository.Create(exam)
return result, err
}
func (s *examService) Update(exam *models.Exam) (*models.Exam, error) {
result, err := s.repository.Update(exam)
return result, err
}
func (s *examService) Delete(id string) error {
err := s.repository.Delete(id)
return err
}