From ab6a57b90795947ecd6a77abf9261044905e4b81 Mon Sep 17 00:00:00 2001 From: JongYeob Sheen Date: Wed, 27 Dec 2023 17:31:49 +0900 Subject: [PATCH] first commit --- .vscode/settings.json | 8 + Makefile | 162 ++ cmd/main.go | 45 + configs/common.dev | 14 + configs/common.go | 16 + configs/common.local | 16 + configs/common.prod | 14 + docker/dev/Dockerfile | 21 + docker/dev/docker-compose.yml | 10 + docker/local/Dockerfile | 21 + docker/local/docker-compose.yml | 10 + docker/prod/Dockerfile | 21 + docker/prod/docker-compose.yml | 10 + docs/docs.go | 2092 +++++++++++++++++ docs/swagger.json | 2066 ++++++++++++++++ docs/swagger.yaml | 1381 +++++++++++ go.mod | 66 + go.sum | 250 ++ internal/controllers/auth.go | 122 + internal/controllers/center.go | 220 ++ internal/controllers/quiz.go | 243 ++ internal/controllers/quizpaper.go | 314 +++ internal/controllers/swagger.go | 22 + internal/controllers/token.go | 53 + internal/controllers/user.go | 131 ++ internal/controllers/userquiz.go | 236 ++ internal/controllers/userquizpaper.go | 331 +++ internal/database/database.go | 44 + internal/helpers/cipher/cipher.go | 86 + internal/helpers/helpers.go | 19 + internal/helpers/securityutil/securityutil.go | 150 ++ internal/middleware/auth.go | 136 ++ internal/middleware/transaction.go | 44 + internal/models/auth.go | 29 + internal/models/center.go | 57 + internal/models/quiz.go | 78 + internal/models/quizpaper.go | 82 + internal/models/token.go | 46 + internal/models/user.go | 52 + internal/models/userquiz.go | 68 + internal/models/userquizpaper.go | 91 + internal/repositories/center.go | 89 + internal/repositories/quiz.go | 81 + internal/repositories/quizpaper.go | 90 + internal/repositories/token.go | 79 + internal/repositories/user.go | 90 + internal/repositories/userquiz.go | 89 + internal/repositories/userquizpaper.go | 87 + internal/routers/auth.go | 53 + internal/routers/center.go | 53 + internal/routers/quiz.go | 50 + internal/routers/quizpaper.go | 57 + internal/routers/router.go | 33 + internal/routers/swagger.go | 37 + internal/routers/token.go | 46 + internal/routers/user.go | 51 + internal/routers/userquiz.go | 53 + internal/routers/userquizpaper.go | 67 + internal/services/auth.go | 72 + internal/services/center.go | 64 + internal/services/quiz.go | 85 + internal/services/quizpaper.go | 56 + internal/services/token.go | 200 ++ internal/services/user.go | 48 + internal/services/userquiz.go | 94 + internal/services/userquizpaper.go | 83 + test/program_test.go | 170 ++ test/quiz_test.go | 172 ++ test/token_test.go | 245 ++ 69 files changed, 11271 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 Makefile create mode 100644 cmd/main.go create mode 100644 configs/common.dev create mode 100644 configs/common.go create mode 100644 configs/common.local create mode 100644 configs/common.prod create mode 100644 docker/dev/Dockerfile create mode 100644 docker/dev/docker-compose.yml create mode 100644 docker/local/Dockerfile create mode 100644 docker/local/docker-compose.yml create mode 100644 docker/prod/Dockerfile create mode 100644 docker/prod/docker-compose.yml create mode 100644 docs/docs.go create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/controllers/auth.go create mode 100644 internal/controllers/center.go create mode 100644 internal/controllers/quiz.go create mode 100644 internal/controllers/quizpaper.go create mode 100644 internal/controllers/swagger.go create mode 100644 internal/controllers/token.go create mode 100644 internal/controllers/user.go create mode 100644 internal/controllers/userquiz.go create mode 100644 internal/controllers/userquizpaper.go create mode 100644 internal/database/database.go create mode 100644 internal/helpers/cipher/cipher.go create mode 100644 internal/helpers/helpers.go create mode 100644 internal/helpers/securityutil/securityutil.go create mode 100644 internal/middleware/auth.go create mode 100644 internal/middleware/transaction.go create mode 100644 internal/models/auth.go create mode 100644 internal/models/center.go create mode 100644 internal/models/quiz.go create mode 100644 internal/models/quizpaper.go create mode 100644 internal/models/token.go create mode 100644 internal/models/user.go create mode 100644 internal/models/userquiz.go create mode 100644 internal/models/userquizpaper.go create mode 100644 internal/repositories/center.go create mode 100644 internal/repositories/quiz.go create mode 100644 internal/repositories/quizpaper.go create mode 100644 internal/repositories/token.go create mode 100644 internal/repositories/user.go create mode 100644 internal/repositories/userquiz.go create mode 100644 internal/repositories/userquizpaper.go create mode 100644 internal/routers/auth.go create mode 100644 internal/routers/center.go create mode 100644 internal/routers/quiz.go create mode 100644 internal/routers/quizpaper.go create mode 100644 internal/routers/router.go create mode 100644 internal/routers/swagger.go create mode 100644 internal/routers/token.go create mode 100644 internal/routers/user.go create mode 100644 internal/routers/userquiz.go create mode 100644 internal/routers/userquizpaper.go create mode 100644 internal/services/auth.go create mode 100644 internal/services/center.go create mode 100644 internal/services/quiz.go create mode 100644 internal/services/quizpaper.go create mode 100644 internal/services/token.go create mode 100644 internal/services/user.go create mode 100644 internal/services/userquiz.go create mode 100644 internal/services/userquizpaper.go create mode 100644 test/program_test.go create mode 100644 test/quiz_test.go create mode 100644 test/token_test.go diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c1b6615 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.detectIndentation": false, + "editor.tabSize": 2, + "editor.formatOnSave": true, + "diffEditor.codeLens": true, + "files.autoSave": "afterDelay", + "editor.wordWrap": "on" +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a4714f8 --- /dev/null +++ b/Makefile @@ -0,0 +1,162 @@ +FUNCTION_NAME =cslms-api +BUILD =$(CURDIR)/build +BIN =$(CURDIR)/bin +MAIN =$(CURDIR)/cmd/main.go +BUCKET =AWS_BUCKET_NAME +REGION =ap-northeast-2 +ARCH =amd64 +PROD_ZIPFILE =$(FUNCTION_NAME)-prod-linux-$(ARCH).zip +PROD_HANDLER =bootstrap +DEV_ZIPFILE =$(FUNCTION_NAME)-dev-linux-$(ARCH).zip +DEV_HANDLER =bootstrap + +default: help + +swag: ## swagger docs + @echo "\033[32mSwagger API Docs ...\033[0m" + @swag init --parseDependency -g cmd/main.go + +deps: ## install dependency + @echo "\033[32mDependency ...\033[0m" + @go install gorm.io/gorm +.PHONY: deps + +env.local: ## copy env for local + @cp configs/common.local configs/common.go + +env.dev: ## copy env for development + @cp configs/common.dev configs/common.go + +env.prod: ## copy env for production + @cp configs/common.prod configs/common.go + +run: env.local ## run local + @echo "\033[32mRunning ...\033[0m" + @go run $(MAIN) +.PHONY: run + +fmt: ## show formatting + @echo "\033[32mfmt ...\033[0m" + @gofmt -s -w . && go mod tidy +.PHONY: fmt + +lint: fmt ## linting + golangci-lint run ./... + +build: deps ## build + @echo "\033[32mBuilding ...\033[0m" + @go build -o $(BIN) $(MAIN) +.PHONY: build + +install: ## install + @echo "\033[32mInstalling ...\033[0m" + go install -v +.PHONY: install + +test: ## testing + @echo "\033[32mTesting...\033[0m" + @go test ./... -v + @echo "all tests passed" +.PHONY: test + +build.local: env.local ## build for local + @echo "\033[32mBuilding for Local running ...\033[0m" + @go build -o $(BUILD)/main $(MAIN) + +build.dev: env.dev ## build for development + @echo "\033[32mBuilding for AWS Lamdbda Development ...\033[0m" + @GOOS=linux GOARCH=$(ARCH) go build -o $(BUILD)/$(DEV_HANDLER) $(MAIN) + @cd $(BUILD) && \ + zip -9 $(DEV_ZIPFILE) $(DEV_HANDLER) + +build.prod: env.prod ## build for production + @echo "\033[32mBuilding for AWS Lamdbda Production ...\033[0m" + @GOOS=linux GOARCH=$(ARCH) go build -o $(BUILD)/$(PROD_HANDLER) $(MAIN) + @cd $(BUILD) && \ + zip -9 $(PROD_ZIPFILE) $(PROD_HANDLER) + +deploy.dev: build.dev lambda.deploy.dev ## deploy for development +deploy.prod: build.prod lambda.deploy.prod ## deploy for production + +lambda.deploy.dev: ## copy to S3 & update lambda function for development + @echo "\033[32mDistribution Development : copy to S3 & update lambda function ...\033[0m" + @aws s3 cp $(BUILD)/$(DEV_ZIPFILE) s3://$(BUCKET) + @aws lambda update-function-code \ + --function-name $(FUNCTION_NAME)-dev \ + --s3-bucket $(BUCKET) \ + --s3-key $(DEV_ZIPFILE) \ + --region $(REGION) \ + &2> /dev/null + +lambda.deploy.prod: ## copy to S3 & update lambda function for production + @echo "\033[32mDistribution Prodction : copy to S3 & update lambda function ...\033[0m" + @aws s3 cp $(BUILD)/$(PROD_ZIPFILE) s3://$(BUCKET) + @aws lambda update-function-code \ + --function-name $(FUNCTION_NAME) \ + --s3-bucket $(BUCKET) \ + --s3-key $(PROD_ZIPFILE) \ + --region $(REGION) \ + &2> /dev/null + + +docker.build.local: ## build docker for local running + @echo "\033[32mDocker for local ...\033[0m" + @docker compose -f docker/local/docker-compose.yml build --no-cache + @docker compose -f docker/local/docker-compose.yml create +.PHONY: docker.build.local + +docker.start.local: ## start docker for local + @echo "\033[32mDocker start ...\033[0m" + @docker compose -f docker/local/docker-compose.yml up -d +.PHONY: docker.start.local + +docker.stop.local: ## stop docker for local + @echo "\033[32mDocker stop ...\033[0m" + @docker compose -f docker/local/docker-compose.yml down +.PHONY: docker.stop.local + + +docker.build.dev: ## build docker for development + @echo "\033[32mDocker for development ...\033[0m" + @docker compose -f docker/dev/docker-compose.yml build --no-cache + @docker compose -f docker/dev/docker-compose.yml create +.PHONY: docker.build.dev + +docker.start.dev: ## start docker for development + @echo "\033[32mDocker start for development...\033[0m" + @docker compose -f docker/dev/docker-compose.yml up -d +.PHONY: docker.start.dev + +docker.stop.dev: ## stop docker for development + @echo "\033[32mDocker stop for development...\033[0m" + @docker compose -f docker/dev/docker-compose.yml down +.PHONY: docker.stop.dev + + +docker.build.prod: ## build docker for production + @echo "\033[32mDocker for production ...\033[0m" + @docker compose -f docker/prod/docker-compose.yml build --no-cache + @docker compose -f docker/prod/docker-compose.yml create +.PHONY: docker.build.prod + +docker.start.prod: ## start docker for production + @echo "\033[32mDocker start for production...\033[0m" + @docker compose -f docker/prod/docker-compose.yml up -d +.PHONY: docker.start.prod + +docker.stop.prod: ## stop docker for production + @echo "\033[32mDocker stop for production...\033[0m" + @docker compose -f docker/prod/docker-compose.yml down +.PHONY: docker.stop.prod + + +clean: ## clean + @echo "\033[32mCleaning...\033[0m" + @go clean + @rm -rf $(BIN)/* + @rm -rf $(BUILD)/* +.PHONY: clean + +help: ## Show help for each of the Makefile recipes. + @grep -E '^[a-zA-Z0-9 -.]+:.*#' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' +.PHONY: help \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..4bbe770 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,45 @@ +package main + +import ( + configs "learnsteam/cslms-api/configs" + _ "learnsteam/cslms-api/docs" + "learnsteam/cslms-api/internal/database" + "learnsteam/cslms-api/internal/helpers" + "learnsteam/cslms-api/internal/routers" + + "log" + "net/http" + + "github.com/apex/gateway" +) + +// @title Learnsteam CodingSchool API +// @version 1.0 +// @description Learnsteam CodingSchool API + +// @contact.name Jay Sheen +// @contact.email sheen@jongyeob.com + +// @securityDefinitions.apikey Bearer +// @in header +// @name Authorization + +func main() { + Init() + Run() +} + +func Init() { + database.Init() + routers.Init() + + database.AutoMigrate() +} + +func Run() { + if helpers.InLambda() { + log.Fatal(gateway.ListenAndServe(configs.PORT, routers.Router)) + } else { + log.Fatal(http.ListenAndServe(configs.PORT, routers.Router)) + } +} diff --git a/configs/common.dev b/configs/common.dev new file mode 100644 index 0000000..69f71f9 --- /dev/null +++ b/configs/common.dev @@ -0,0 +1,14 @@ +package config + +const ( + BASE_URL = "https://cslms-api.jongyeob.com" + PORT = ":3030" + DATABASE_URL = "root:fbOgZ6Xxn5VXBYihjqygRXyaK6ZUgKL6@tcp(learnsteam-quiz-db:3306)/learnsteam_quiz?charset=utf8&parseTime=True&loc=Local" + + KAKAO_CLIENT_ID = "986830" + KAKAO_CLIENT_SECRET = "14f63a8e91c4e0fe88bc40e3ff348233" + + SECRET_KEY = "GmhiMJuAIyF3jwkd97iODSoJoN3bIVkF" + ACCESS_KEY = "NQD9AXmpn13asz84oPf5Dyc9rhLyhmHp" + REFRESH_KEY = "qFzFMlX2PfTSXgA9QPFltcNhJk1SVF0a" +) diff --git a/configs/common.go b/configs/common.go new file mode 100644 index 0000000..f54c01a --- /dev/null +++ b/configs/common.go @@ -0,0 +1,16 @@ +package config + +const ( + BASE_URL = "http://localhost:3030" + PORT = ":3030" + DATABASE_URL = "cslms:atobot2013!#%@tcp(localhost:33061)/cslms5?charset=utf8&parseTime=True&loc=Local" + //DATABASE_URL = "learnsteam:fbOgZ6Xxn5VXBYihjqygRXyaK6ZUgKL6@tcp(localhost:3306)/learnsteam_quiz?charset=utf8&parseTime=True&loc=Local" + //DATABASE_URL = "root:fbOgZ6Xxn5VXBYihjqygRXyaK6ZUgKL6@tcp(learnsteam-quiz-db:3306)/learnsteam_quiz?charset=utf8&parseTime=True&loc=Local" + + KAKAO_CLIENT_ID = "986830" + KAKAO_CLIENT_SECRET = "14f63a8e91c4e0fe88bc40e3ff348233" + + SECRET_KEY = "GmhiMJuAIyF3jwkd97iODSoJoN3bIVkF" + ACCESS_KEY = "NQD9AXmpn13asz84oPf5Dyc9rhLyhmHp" + REFRESH_KEY = "qFzFMlX2PfTSXgA9QPFltcNhJk1SVF0a" +) diff --git a/configs/common.local b/configs/common.local new file mode 100644 index 0000000..f54c01a --- /dev/null +++ b/configs/common.local @@ -0,0 +1,16 @@ +package config + +const ( + BASE_URL = "http://localhost:3030" + PORT = ":3030" + DATABASE_URL = "cslms:atobot2013!#%@tcp(localhost:33061)/cslms5?charset=utf8&parseTime=True&loc=Local" + //DATABASE_URL = "learnsteam:fbOgZ6Xxn5VXBYihjqygRXyaK6ZUgKL6@tcp(localhost:3306)/learnsteam_quiz?charset=utf8&parseTime=True&loc=Local" + //DATABASE_URL = "root:fbOgZ6Xxn5VXBYihjqygRXyaK6ZUgKL6@tcp(learnsteam-quiz-db:3306)/learnsteam_quiz?charset=utf8&parseTime=True&loc=Local" + + KAKAO_CLIENT_ID = "986830" + KAKAO_CLIENT_SECRET = "14f63a8e91c4e0fe88bc40e3ff348233" + + SECRET_KEY = "GmhiMJuAIyF3jwkd97iODSoJoN3bIVkF" + ACCESS_KEY = "NQD9AXmpn13asz84oPf5Dyc9rhLyhmHp" + REFRESH_KEY = "qFzFMlX2PfTSXgA9QPFltcNhJk1SVF0a" +) diff --git a/configs/common.prod b/configs/common.prod new file mode 100644 index 0000000..69f71f9 --- /dev/null +++ b/configs/common.prod @@ -0,0 +1,14 @@ +package config + +const ( + BASE_URL = "https://cslms-api.jongyeob.com" + PORT = ":3030" + DATABASE_URL = "root:fbOgZ6Xxn5VXBYihjqygRXyaK6ZUgKL6@tcp(learnsteam-quiz-db:3306)/learnsteam_quiz?charset=utf8&parseTime=True&loc=Local" + + KAKAO_CLIENT_ID = "986830" + KAKAO_CLIENT_SECRET = "14f63a8e91c4e0fe88bc40e3ff348233" + + SECRET_KEY = "GmhiMJuAIyF3jwkd97iODSoJoN3bIVkF" + ACCESS_KEY = "NQD9AXmpn13asz84oPf5Dyc9rhLyhmHp" + REFRESH_KEY = "qFzFMlX2PfTSXgA9QPFltcNhJk1SVF0a" +) diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile new file mode 100644 index 0000000..8918605 --- /dev/null +++ b/docker/dev/Dockerfile @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1 + +FROM alpine:latest AS deps +RUN apk add --no-cache mariadb-dev sqlite-dev build-base go + +FROM deps AS builder +WORKDIR /app +COPY . . +RUN cp configs/common.dev configs/common.go +RUN go mod download && go mod verify +RUN CGO_ENABLED=1 go build -v -o bootstrap cmd/main.go + +FROM alpine:latest AS runner +WORKDIR /app +RUN apk add --no-cache sqlite-libs mariadb-connector-c libgcc +COPY --from=builder /app/bootstrap . + +EXPOSE 3030 +ENV PORT 3030 + +CMD ["sh", "-c", "./bootstrap"] \ No newline at end of file diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml new file mode 100644 index 0000000..b02d84f --- /dev/null +++ b/docker/dev/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3" + +services: + cslms-api: + build: + context: ../../ + dockerfile: docker/dev/Dockerfile + image: learnsteam/cslms-api:dev + ports: + - "3030:3030" diff --git a/docker/local/Dockerfile b/docker/local/Dockerfile new file mode 100644 index 0000000..5d37a53 --- /dev/null +++ b/docker/local/Dockerfile @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1 + +FROM golang:alpine AS deps +RUN apk add --no-cache mariadb-dev sqlite-dev build-base + +FROM deps AS builder +WORKDIR /app +COPY . . +RUN cp configs/common.local configs/common.go +RUN go mod download && go mod verify +RUN CGO_ENABLED=1 go build -v -o bootstrap cmd/main.go + +FROM alpine:latest AS runner +WORKDIR /app +RUN apk add --no-cache sqlite-libs mariadb-connector-c libgcc +COPY --from=builder /app/bootstrap . + +EXPOSE 3030 +ENV PORT 3030 + +CMD ["sh", "-c", "./bootstrap"] \ No newline at end of file diff --git a/docker/local/docker-compose.yml b/docker/local/docker-compose.yml new file mode 100644 index 0000000..89b20d5 --- /dev/null +++ b/docker/local/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3" + +services: + cslms-api: + build: + context: ../../ + dockerfile: docker/local/Dockerfile + image: learnsteam/cslms-api:local + ports: + - "3030:3030" diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile new file mode 100644 index 0000000..1a91329 --- /dev/null +++ b/docker/prod/Dockerfile @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1 + +FROM alpine:latest AS deps +RUN apk add --no-cache mariadb-dev sqlite-dev build-base go + +FROM deps AS builder +WORKDIR /app +COPY . . +RUN cp configs/common.prod configs/common.go +RUN go mod download && go mod verify +RUN CGO_ENABLED=1 go build -v -o bootstrap cmd/main.go + +FROM alpine:latest AS runner +WORKDIR /app +RUN apk add --no-cache sqlite-libs mariadb-connector-c libgcc +COPY --from=builder /app/bootstrap . + +EXPOSE 3030 +ENV PORT 3030 + +CMD ["sh", "-c", "./bootstrap"] \ No newline at end of file diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml new file mode 100644 index 0000000..4aa831d --- /dev/null +++ b/docker/prod/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3" + +services: + cslms-api: + build: + context: ../../ + dockerfile: docker/prod/Dockerfile + image: learnsteam/cslms-api + ports: + - "3030:3030" diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..c211de8 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,2092 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Jay Sheen", + "email": "sheen@jongyeob.com" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/auth/login": { + "post": { + "description": "사용자가 로그인합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "로그인" + ], + "summary": "사용자 로그인 로그인", + "parameters": [ + { + "description": "Login Body", + "name": "loginBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.LoginResponse" + } + } + } + } + }, + "/auth/register": { + "post": { + "description": "회원가입", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "회원가입" + ], + "summary": "회원가입", + "parameters": [ + { + "description": "Register Body", + "name": "registerBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.RegisterRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.RegisterResponse" + } + } + } + } + }, + "/center": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "센터 목록을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "센터" + ], + "summary": "센터 목록 가져오기", + "parameters": [ + { + "type": "string", + "description": "검색어", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "페이지", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "페이지 사이즈", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.CenterListResponse" + } + } + } + } + }, + "/center/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "ID로 센터 정보를 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "센터" + ], + "summary": "센터 정보 가져오기", + "parameters": [ + { + "type": "string", + "description": "센터 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.Center" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "센터를 수정합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "센터" + ], + "summary": "센터 수정", + "parameters": [ + { + "type": "integer", + "description": "Center ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Center Body", + "name": "centerBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.CenterRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.Center" + } + } + } + } + }, + "/quiz": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈 목록을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈" + ], + "summary": "퀴즈 목록 가져오기", + "parameters": [ + { + "type": "string", + "description": "퀴즈페이퍼 ID", + "name": "quiz_paper_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "검색어", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "페이지", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "페이지 사이즈", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizListResponse" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈를 만듭니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈" + ], + "summary": "퀴즈 생성", + "parameters": [ + { + "description": "Quiz Body", + "name": "quizBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizResponse" + } + } + } + } + }, + "/quiz/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "ID로 퀴즈를 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈" + ], + "summary": "퀴즈 가져오기", + "parameters": [ + { + "type": "string", + "description": "퀴즈 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizResponse" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈를 수정합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈" + ], + "summary": "퀴즈 수정", + "parameters": [ + { + "type": "integer", + "description": "Quiz ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Quiz Body", + "name": "quizBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizResponse" + } + } + } + } + }, + "/quizpaper": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈페이퍼 목록을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈페이퍼" + ], + "summary": "퀴즈페이퍼 목록 가져오기", + "parameters": [ + { + "type": "string", + "description": "태그", + "name": "tag", + "in": "query" + }, + { + "type": "string", + "description": "검색어", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "페이지", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "페이지 사이즈", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperListResponse" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈 퀴즈페이퍼을 만듭니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈페이퍼" + ], + "summary": "퀴즈 퀴즈페이퍼 생성", + "parameters": [ + { + "description": "QuizPaper Body", + "name": "quizPaperBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperResponse" + } + } + } + } + }, + "/quizpaper/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "ID로 퀴즈 퀴즈페이퍼을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈페이퍼" + ], + "summary": "퀴즈 퀴즈페이퍼 가져오기", + "parameters": [ + { + "type": "string", + "description": "퀴즈 퀴즈페이퍼 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperResponse" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈 퀴즈페이퍼을 수정합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈페이퍼" + ], + "summary": "퀴즈 퀴즈페이퍼 수정", + "parameters": [ + { + "type": "string", + "description": "퀴즈 퀴즈페이퍼 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "QuizPaper Body", + "name": "quizPaperUpdateBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperResponse" + } + } + } + } + }, + "/quizpaper/{id}/copy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈 퀴즈페이퍼와 퀴즈를 복사합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈페이퍼" + ], + "summary": "퀴즈 퀴즈페이퍼 복사", + "parameters": [ + { + "type": "string", + "description": "퀴즈페이퍼 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "QuizPaper Copy Body", + "name": "quizPaperCopyBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperCopyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperCopyResponse" + } + } + } + } + }, + "/token/refresh": { + "post": { + "description": "AccessToken을 RefreshToken으로 갱신합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Token" + ], + "summary": "AccessToken Refresh", + "parameters": [ + { + "description": "RefreshToken", + "name": "refresh_token", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.Token" + } + } + } + } + }, + "/user": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "사용자 목록을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자" + ], + "summary": "사용자 목록 가져오기", + "parameters": [ + { + "type": "string", + "description": "검색어", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "페이지", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "페이지 사이즈", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserListResponse" + } + } + } + } + }, + "/user/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "ID로 사용자 정보를 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자" + ], + "summary": "사용자 정보 가져오기", + "parameters": [ + { + "type": "string", + "description": "사용자 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.User" + } + } + } + } + }, + "/userquiz": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "사용자퀴즈 목록을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈" + ], + "summary": "사용자퀴즈 목록 가져오기", + "parameters": [ + { + "type": "string", + "description": "사용자퀴즈페이퍼 ID", + "name": "user_quiz_paper_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "검색어", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "페이지", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "페이지 사이즈", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizListResponse" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "사용자퀴즈를 만듭니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈" + ], + "summary": "사용자퀴즈 생성", + "parameters": [ + { + "description": "UserQuiz Body", + "name": "quizBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizResponse" + } + } + } + } + }, + "/userquiz/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "ID로 사용자퀴즈를 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈" + ], + "summary": "사용자퀴즈 가져오기", + "parameters": [ + { + "type": "string", + "description": "사용자퀴즈 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizResponse" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "사용자퀴즈를 수정합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈" + ], + "summary": "사용자퀴즈 수정", + "parameters": [ + { + "type": "integer", + "description": "Quiz ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Quiz Body", + "name": "quizBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizResponse" + } + } + } + } + }, + "/userquizpaper": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "응시 목록을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈페이퍼" + ], + "summary": "응시 목록 가져오기", + "parameters": [ + { + "type": "string", + "description": "검색어", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "페이지", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "페이지 사이즈", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperListResponse" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "응시 매칭을 만듭니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈페이퍼" + ], + "summary": "응시 매칭 생성", + "parameters": [ + { + "description": "UserQuizPaper Body", + "name": "userQuizPaperBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaper" + } + } + } + } + } + }, + "/userquizpaper/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "ID로 응시 정보를 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈페이퍼" + ], + "summary": "응시 정보 가져오기", + "parameters": [ + { + "type": "string", + "description": "응시 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperResponse" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "응시를 수정합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈페이퍼" + ], + "summary": "응시 수정", + "parameters": [ + { + "type": "integer", + "description": "UserQuizPaper ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "UserQuizPaper Update Body", + "name": "userQuizPaperUpdateBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperResponse" + } + } + } + }, + "patch": { + "security": [ + { + "Bearer": [] + } + ], + "description": "응시 정보를 변경합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈페이퍼" + ], + "summary": "응시 정보 변경", + "parameters": [ + { + "type": "integer", + "description": "UserQuizPaper ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "UserQuizPaper Patch Body (변경할 필드만 입력)", + "name": "userQuizPaperPatchBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperPatchRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperResponse" + } + } + } + } + } + }, + "definitions": { + "learnsteam_cslms-api_internal_models.Center": { + "type": "object", + "properties": { + "center_name": { + "type": "string", + "example": "learnsteam_kd" + }, + "center_title": { + "type": "string", + "example": "강동런스팀로봇코딩학원" + }, + "company_info": { + "type": "string", + "example": "사업자정보-json 기타 정보 추가 가능" + }, + "content_page": { + "type": "string", + "example": "학원상세페이지, html/마크다운/text" + }, + "guid_id": { + "type": "string", + "example": "2036023a-fb56-4b6c-b3bb-c787c681ada6" + }, + "id": { + "type": "integer", + "example": 100001 + }, + "memo": { + "type": "string", + "example": "메모" + }, + "owner_id": { + "type": "integer", + "example": 100001 + }, + "status": { + "type": "string", + "example": "on" + } + } + }, + "learnsteam_cslms-api_internal_models.CenterListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.Center" + } + }, + "page": { + "type": "integer", + "example": 1 + }, + "pageSize": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 5 + }, + "totalPage": { + "type": "integer", + "example": 1 + } + } + }, + "learnsteam_cslms-api_internal_models.CenterRequest": { + "type": "object", + "properties": { + "center_name": { + "type": "string", + "example": "learnsteam_kd" + }, + "center_title": { + "type": "string", + "example": "강동런스팀로봇코딩학원" + }, + "company_info": { + "type": "string", + "example": "사업자정보-json 기타 정보 추가 가능" + }, + "content_page": { + "type": "string", + "example": "학원상세페이지, html/마크다운/text" + }, + "memo": { + "type": "string", + "example": "메모" + }, + "owner_id": { + "type": "integer", + "example": 100001 + }, + "status": { + "type": "string", + "example": "on" + } + } + }, + "learnsteam_cslms-api_internal_models.LoginRequest": { + "type": "object", + "properties": { + "password": { + "type": "string", + "example": "testme" + }, + "user_name": { + "type": "string", + "example": "admin0" + } + } + }, + "learnsteam_cslms-api_internal_models.LoginResponse": { + "type": "object", + "properties": { + "refresh_token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDc4OTcwMjcs" + }, + "token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDI3MTMwMjcsInN1" + }, + "user": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.User" + } + } + }, + "learnsteam_cslms-api_internal_models.Quiz": { + "type": "object", + "properties": { + "answer": { + "type": "array", + "items": { + "type": "integer" + } + }, + "center_id": { + "type": "integer", + "example": 100001 + }, + "content": { + "type": "array", + "items": { + "type": "integer" + } + }, + "guid_id": { + "type": "string", + "example": "2036023a-fb56-4b6c-b3bb-c787c681ada6" + }, + "id": { + "type": "integer", + "example": 1000001 + }, + "question": { + "type": "string", + "example": "퀴즈 질문입니다." + }, + "question_type": { + "type": "string", + "example": "choice" + }, + "quiz_paper_id": { + "type": "integer", + "example": 100001 + }, + "vol_no": { + "type": "integer", + "example": 5 + } + } + }, + "learnsteam_cslms-api_internal_models.QuizContent": { + "type": "object", + "properties": { + "hint": { + "type": "string", + "example": "markdown문서로 작성됨" + }, + "option1": { + "type": "string", + "example": "markdown 문서로 작성됨" + }, + "option2": { + "type": "string", + "example": "markdown 문서로 작성됨" + }, + "option3": { + "type": "string", + "example": "markdown 문서로 작성됨" + }, + "option4": { + "type": "string", + "example": "markdown 문서로 작성됨" + } + } + }, + "learnsteam_cslms-api_internal_models.QuizListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.Quiz" + } + }, + "page": { + "type": "integer", + "example": 1 + }, + "pageSize": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 5 + }, + "totalPage": { + "type": "integer", + "example": 1 + } + } + }, + "learnsteam_cslms-api_internal_models.QuizPaper": { + "type": "object", + "properties": { + "category": { + "type": "string", + "example": "파이썬기본" + }, + "center_id": { + "type": "integer", + "example": 100001 + }, + "content": { + "type": "string", + "example": "퀴즈 시트 설명" + }, + "guid_id": { + "type": "string", + "example": "ef74c59a-c707-4162-a52b-455906c81ec1" + }, + "id": { + "type": "integer", + "example": 100001 + }, + "manager_id": { + "type": "integer", + "example": 100001 + }, + "status": { + "type": "string", + "example": "on" + }, + "tag": { + "type": "array", + "items": { + "type": "integer" + } + }, + "title": { + "type": "string", + "example": "퀴즈 시트 제목" + } + } + }, + "learnsteam_cslms-api_internal_models.QuizPaperCopyRequest": { + "type": "object", + "properties": { + "center_id": { + "type": "integer", + "example": 100002 + }, + "title": { + "type": "string", + "example": "퀴즈 시트 제목" + } + } + }, + "learnsteam_cslms-api_internal_models.QuizPaperCopyResponse": { + "type": "object", + "properties": { + "quiz": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.Quiz" + } + }, + "quiz_paper": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaper" + } + } + }, + "learnsteam_cslms-api_internal_models.QuizPaperListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaper" + } + }, + "page": { + "type": "integer", + "example": 1 + }, + "pageSize": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 999 + }, + "totalPage": { + "type": "integer", + "example": 99 + } + } + }, + "learnsteam_cslms-api_internal_models.QuizPaperRequest": { + "type": "object", + "properties": { + "category": { + "type": "string", + "example": "파이썬기본" + }, + "center_id": { + "type": "integer", + "example": 100001 + }, + "content": { + "type": "string", + "example": "퀴즈 시트 설명" + }, + "status": { + "type": "string", + "example": "on" + }, + "tag": { + "type": "array", + "items": { + "type": "integer" + } + }, + "title": { + "type": "string", + "example": "퀴즈 시트 제목" + } + } + }, + "learnsteam_cslms-api_internal_models.QuizPaperResponse": { + "type": "object", + "properties": { + "category": { + "type": "string", + "example": "파이썬기본" + }, + "center_id": { + "type": "integer", + "example": 100001 + }, + "content": { + "type": "string", + "example": "퀴즈 시트 설명" + }, + "id": { + "type": "integer", + "example": 100001 + }, + "manager_id": { + "type": "integer", + "example": 100001 + }, + "status": { + "type": "string", + "example": "on" + }, + "tag": { + "type": "array", + "items": { + "type": "integer" + } + }, + "title": { + "type": "string", + "example": "퀴즈 시트 제목" + } + } + }, + "learnsteam_cslms-api_internal_models.QuizRequest": { + "type": "object", + "properties": { + "answer": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "option1", + "option2" + ] + }, + "center_id": { + "type": "integer", + "example": 1000001 + }, + "content": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizContent" + }, + "question": { + "type": "string", + "example": "질문입니다." + }, + "question_type": { + "type": "string", + "example": "choice" + }, + "quiz_paper_id": { + "type": "integer", + "example": 1000001 + }, + "vol_no": { + "type": "integer", + "example": 1 + } + } + }, + "learnsteam_cslms-api_internal_models.QuizResponse": { + "type": "object", + "properties": { + "answer": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "option1", + "option2" + ] + }, + "content": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizContent" + }, + "id": { + "type": "integer", + "example": 1000001 + }, + "question": { + "type": "string", + "example": "퀴즈 질문입니다." + }, + "question_type": { + "type": "string", + "example": "check" + }, + "quiz_paper_id": { + "type": "integer", + "example": 1000001 + }, + "vol_no": { + "type": "integer", + "example": 5 + } + } + }, + "learnsteam_cslms-api_internal_models.RegisterRequest": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "example": "길순" + }, + "gender": { + "type": "string", + "example": "F" + }, + "last_name": { + "type": "string", + "example": "홍" + }, + "password": { + "type": "string", + "example": "StrongPass!@#$" + }, + "phone_cs": { + "type": "string", + "example": "01012345678" + }, + "upload_image": { + "type": "string", + "example": "" + }, + "user_name": { + "type": "string", + "example": "gilsoon" + }, + "user_role": { + "type": "string", + "example": "member" + } + } + }, + "learnsteam_cslms-api_internal_models.RegisterResponse": { + "type": "object", + "properties": { + "refresh_token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDc4OTcwMjcs" + }, + "token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDI3MTMwMjcsInN1" + }, + "user": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.User" + } + } + }, + "learnsteam_cslms-api_internal_models.Token": { + "type": "object", + "properties": { + "ending_at": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "register_at": { + "type": "string" + }, + "status": { + "type": "string" + }, + "token": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "learnsteam_cslms-api_internal_models.User": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "example": "길순" + }, + "gender": { + "type": "string", + "example": "F" + }, + "guid_id": { + "type": "string", + "example": "137c1683-2ad6-4201-b256-253828b61c49" + }, + "id": { + "type": "integer", + "example": 100001 + }, + "last_name": { + "type": "string", + "example": "홍" + }, + "memo_cs": { + "type": "string", + "example": "사용자 메모" + }, + "phone_cs": { + "type": "string", + "example": "010-1234-5678" + }, + "register_at": { + "type": "string" + }, + "upload_image": { + "type": "string", + "example": "image_url" + }, + "user_name": { + "type": "string", + "example": "user0" + }, + "user_role": { + "type": "string", + "example": "member" + } + } + }, + "learnsteam_cslms-api_internal_models.UserListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.User" + } + }, + "page": { + "type": "integer", + "example": 1 + }, + "pageSize": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 90 + }, + "totalPage": { + "type": "integer", + "example": 9 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizResponse" + } + }, + "page": { + "type": "integer", + "example": 1 + }, + "pageSize": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 5 + }, + "totalPage": { + "type": "integer", + "example": 1 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizPaper": { + "type": "object", + "properties": { + "center_id": { + "type": "integer", + "example": 1000015 + }, + "done_at": { + "type": "string" + }, + "guid_id": { + "type": "string", + "example": "7f9329f5-2e36-4638-92d2-73064b7291a4" + }, + "id": { + "type": "integer", + "example": 1000015 + }, + "quiz_paper_id": { + "type": "integer", + "example": 1000001 + }, + "start_at": { + "type": "string" + }, + "status": { + "type": "string", + "example": "wating" + }, + "total_score": { + "type": "number", + "example": 100 + }, + "user_id": { + "type": "integer", + "example": 1000002 + }, + "user_score": { + "type": "number", + "example": 5 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizPaperListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaper" + } + }, + "page": { + "type": "integer", + "example": 1 + }, + "pageSize": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 999 + }, + "totalPage": { + "type": "integer", + "example": 99 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizPaperPatchRequest": { + "type": "object", + "properties": { + "done_at": { + "type": "string", + "example": "2023-11-10T13:25:00+09:00" + }, + "start_at": { + "type": "string", + "example": "2023-11-10T13:10:00+09:00" + }, + "status": { + "type": "string", + "example": "ready" + }, + "total_score": { + "type": "number", + "example": 80 + }, + "user_score": { + "type": "number", + "example": 4 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizPaperRequest": { + "type": "object", + "properties": { + "quiz_paper_id": { + "type": "integer", + "example": 1000002 + }, + "users": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizPaperResponse": { + "type": "object", + "properties": { + "center_id": { + "type": "integer", + "example": 1000015 + }, + "done_at": { + "type": "string" + }, + "guid_id": { + "type": "string", + "example": "7f9329f5-2e36-4638-92d2-73064b7291a4" + }, + "id": { + "type": "integer", + "example": 1000015 + }, + "quiz_paper_id": { + "type": "integer", + "example": 1000001 + }, + "start_at": { + "type": "string" + }, + "status": { + "type": "string", + "example": "wating" + }, + "total_score": { + "type": "number", + "example": 100 + }, + "user_id": { + "type": "integer", + "example": 1000002 + }, + "user_score": { + "type": "number", + "example": 5 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizPaperUpdateRequest": { + "type": "object", + "properties": { + "done_at": { + "type": "string", + "example": "2023-11-10T13:25:00+09:00" + }, + "quiz_paper_id": { + "type": "integer", + "example": 1000002 + }, + "start_at": { + "type": "string", + "example": "2023-11-10T13:10:00+09:00" + }, + "status": { + "type": "string", + "example": "ready" + }, + "total_score": { + "type": "number", + "example": 100 + }, + "user_id": { + "type": "integer", + "example": 1000003 + }, + "user_score": { + "type": "number", + "example": 5 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizRequest": { + "type": "object", + "properties": { + "answer": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "option1", + "option2" + ] + }, + "center_id": { + "type": "integer", + "example": 1000001 + }, + "content": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizContent" + }, + "question": { + "type": "string", + "example": "질문입니다." + }, + "question_type": { + "type": "string", + "example": "choice" + }, + "quiz_paper_id": { + "type": "integer", + "example": 1000001 + }, + "result": { + "type": "string", + "example": "success" + }, + "score": { + "type": "number", + "example": 10 + }, + "status": { + "type": "string", + "example": "waiting" + }, + "user_id": { + "type": "integer", + "example": 1000001 + }, + "vol_no": { + "type": "integer", + "example": 1 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizResponse": { + "type": "object", + "properties": { + "answer": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "option1", + "option2" + ] + }, + "center_id": { + "type": "string", + "example": "2036023a-fb56-4b6c-b3bb-c787c681ada6" + }, + "content": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizContent" + }, + "guid_id": { + "type": "string", + "example": "2036023a-fb56-4b6c-b3bb-c787c681ada6" + }, + "id": { + "type": "integer", + "example": 1000001 + }, + "question": { + "type": "string", + "example": "퀴즈 질문입니다." + }, + "question_type": { + "type": "string", + "example": "check" + }, + "result": { + "type": "string", + "example": "success" + }, + "score": { + "type": "number", + "example": 10 + }, + "status": { + "type": "string", + "example": "waiting" + }, + "user_id": { + "type": "integer", + "example": 1000001 + }, + "user_quiz_paper_id": { + "type": "integer", + "example": 1000001 + }, + "vol_no": { + "type": "integer", + "example": 5 + } + } + } + }, + "securityDefinitions": { + "Bearer": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "Learnsteam CodingSchool API", + Description: "Learnsteam CodingSchool API", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..4fdfcb3 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,2066 @@ +{ + "swagger": "2.0", + "info": { + "description": "Learnsteam CodingSchool API", + "title": "Learnsteam CodingSchool API", + "contact": { + "name": "Jay Sheen", + "email": "sheen@jongyeob.com" + }, + "version": "1.0" + }, + "paths": { + "/auth/login": { + "post": { + "description": "사용자가 로그인합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "로그인" + ], + "summary": "사용자 로그인 로그인", + "parameters": [ + { + "description": "Login Body", + "name": "loginBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.LoginRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.LoginResponse" + } + } + } + } + }, + "/auth/register": { + "post": { + "description": "회원가입", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "회원가입" + ], + "summary": "회원가입", + "parameters": [ + { + "description": "Register Body", + "name": "registerBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.RegisterRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.RegisterResponse" + } + } + } + } + }, + "/center": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "센터 목록을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "센터" + ], + "summary": "센터 목록 가져오기", + "parameters": [ + { + "type": "string", + "description": "검색어", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "페이지", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "페이지 사이즈", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.CenterListResponse" + } + } + } + } + }, + "/center/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "ID로 센터 정보를 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "센터" + ], + "summary": "센터 정보 가져오기", + "parameters": [ + { + "type": "string", + "description": "센터 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.Center" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "센터를 수정합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "센터" + ], + "summary": "센터 수정", + "parameters": [ + { + "type": "integer", + "description": "Center ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Center Body", + "name": "centerBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.CenterRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.Center" + } + } + } + } + }, + "/quiz": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈 목록을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈" + ], + "summary": "퀴즈 목록 가져오기", + "parameters": [ + { + "type": "string", + "description": "퀴즈페이퍼 ID", + "name": "quiz_paper_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "검색어", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "페이지", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "페이지 사이즈", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizListResponse" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈를 만듭니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈" + ], + "summary": "퀴즈 생성", + "parameters": [ + { + "description": "Quiz Body", + "name": "quizBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizResponse" + } + } + } + } + }, + "/quiz/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "ID로 퀴즈를 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈" + ], + "summary": "퀴즈 가져오기", + "parameters": [ + { + "type": "string", + "description": "퀴즈 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizResponse" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈를 수정합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈" + ], + "summary": "퀴즈 수정", + "parameters": [ + { + "type": "integer", + "description": "Quiz ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Quiz Body", + "name": "quizBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizResponse" + } + } + } + } + }, + "/quizpaper": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈페이퍼 목록을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈페이퍼" + ], + "summary": "퀴즈페이퍼 목록 가져오기", + "parameters": [ + { + "type": "string", + "description": "태그", + "name": "tag", + "in": "query" + }, + { + "type": "string", + "description": "검색어", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "페이지", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "페이지 사이즈", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperListResponse" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈 퀴즈페이퍼을 만듭니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈페이퍼" + ], + "summary": "퀴즈 퀴즈페이퍼 생성", + "parameters": [ + { + "description": "QuizPaper Body", + "name": "quizPaperBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperResponse" + } + } + } + } + }, + "/quizpaper/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "ID로 퀴즈 퀴즈페이퍼을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈페이퍼" + ], + "summary": "퀴즈 퀴즈페이퍼 가져오기", + "parameters": [ + { + "type": "string", + "description": "퀴즈 퀴즈페이퍼 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperResponse" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈 퀴즈페이퍼을 수정합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈페이퍼" + ], + "summary": "퀴즈 퀴즈페이퍼 수정", + "parameters": [ + { + "type": "string", + "description": "퀴즈 퀴즈페이퍼 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "QuizPaper Body", + "name": "quizPaperUpdateBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperResponse" + } + } + } + } + }, + "/quizpaper/{id}/copy": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "퀴즈 퀴즈페이퍼와 퀴즈를 복사합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "퀴즈페이퍼" + ], + "summary": "퀴즈 퀴즈페이퍼 복사", + "parameters": [ + { + "type": "string", + "description": "퀴즈페이퍼 ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "QuizPaper Copy Body", + "name": "quizPaperCopyBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperCopyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaperCopyResponse" + } + } + } + } + }, + "/token/refresh": { + "post": { + "description": "AccessToken을 RefreshToken으로 갱신합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Token" + ], + "summary": "AccessToken Refresh", + "parameters": [ + { + "description": "RefreshToken", + "name": "refresh_token", + "in": "body", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.Token" + } + } + } + } + }, + "/user": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "사용자 목록을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자" + ], + "summary": "사용자 목록 가져오기", + "parameters": [ + { + "type": "string", + "description": "검색어", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "페이지", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "페이지 사이즈", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserListResponse" + } + } + } + } + }, + "/user/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "ID로 사용자 정보를 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자" + ], + "summary": "사용자 정보 가져오기", + "parameters": [ + { + "type": "string", + "description": "사용자 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.User" + } + } + } + } + }, + "/userquiz": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "사용자퀴즈 목록을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈" + ], + "summary": "사용자퀴즈 목록 가져오기", + "parameters": [ + { + "type": "string", + "description": "사용자퀴즈페이퍼 ID", + "name": "user_quiz_paper_id", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "검색어", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "페이지", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "페이지 사이즈", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizListResponse" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "사용자퀴즈를 만듭니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈" + ], + "summary": "사용자퀴즈 생성", + "parameters": [ + { + "description": "UserQuiz Body", + "name": "quizBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizResponse" + } + } + } + } + }, + "/userquiz/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "ID로 사용자퀴즈를 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈" + ], + "summary": "사용자퀴즈 가져오기", + "parameters": [ + { + "type": "string", + "description": "사용자퀴즈 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizResponse" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "사용자퀴즈를 수정합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈" + ], + "summary": "사용자퀴즈 수정", + "parameters": [ + { + "type": "integer", + "description": "Quiz ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Quiz Body", + "name": "quizBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizResponse" + } + } + } + } + }, + "/userquizpaper": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "응시 목록을 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈페이퍼" + ], + "summary": "응시 목록 가져오기", + "parameters": [ + { + "type": "string", + "description": "검색어", + "name": "q", + "in": "query" + }, + { + "type": "integer", + "description": "페이지", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "페이지 사이즈", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperListResponse" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "응시 매칭을 만듭니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈페이퍼" + ], + "summary": "응시 매칭 생성", + "parameters": [ + { + "description": "UserQuizPaper Body", + "name": "userQuizPaperBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaper" + } + } + } + } + } + }, + "/userquizpaper/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "ID로 응시 정보를 가져옵니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈페이퍼" + ], + "summary": "응시 정보 가져오기", + "parameters": [ + { + "type": "string", + "description": "응시 ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperResponse" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "응시를 수정합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈페이퍼" + ], + "summary": "응시 수정", + "parameters": [ + { + "type": "integer", + "description": "UserQuizPaper ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "UserQuizPaper Update Body", + "name": "userQuizPaperUpdateBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperResponse" + } + } + } + }, + "patch": { + "security": [ + { + "Bearer": [] + } + ], + "description": "응시 정보를 변경합니다.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "사용자퀴즈페이퍼" + ], + "summary": "응시 정보 변경", + "parameters": [ + { + "type": "integer", + "description": "UserQuizPaper ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "UserQuizPaper Patch Body (변경할 필드만 입력)", + "name": "userQuizPaperPatchBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperPatchRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperResponse" + } + } + } + } + } + }, + "definitions": { + "learnsteam_cslms-api_internal_models.Center": { + "type": "object", + "properties": { + "center_name": { + "type": "string", + "example": "learnsteam_kd" + }, + "center_title": { + "type": "string", + "example": "강동런스팀로봇코딩학원" + }, + "company_info": { + "type": "string", + "example": "사업자정보-json 기타 정보 추가 가능" + }, + "content_page": { + "type": "string", + "example": "학원상세페이지, html/마크다운/text" + }, + "guid_id": { + "type": "string", + "example": "2036023a-fb56-4b6c-b3bb-c787c681ada6" + }, + "id": { + "type": "integer", + "example": 100001 + }, + "memo": { + "type": "string", + "example": "메모" + }, + "owner_id": { + "type": "integer", + "example": 100001 + }, + "status": { + "type": "string", + "example": "on" + } + } + }, + "learnsteam_cslms-api_internal_models.CenterListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.Center" + } + }, + "page": { + "type": "integer", + "example": 1 + }, + "pageSize": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 5 + }, + "totalPage": { + "type": "integer", + "example": 1 + } + } + }, + "learnsteam_cslms-api_internal_models.CenterRequest": { + "type": "object", + "properties": { + "center_name": { + "type": "string", + "example": "learnsteam_kd" + }, + "center_title": { + "type": "string", + "example": "강동런스팀로봇코딩학원" + }, + "company_info": { + "type": "string", + "example": "사업자정보-json 기타 정보 추가 가능" + }, + "content_page": { + "type": "string", + "example": "학원상세페이지, html/마크다운/text" + }, + "memo": { + "type": "string", + "example": "메모" + }, + "owner_id": { + "type": "integer", + "example": 100001 + }, + "status": { + "type": "string", + "example": "on" + } + } + }, + "learnsteam_cslms-api_internal_models.LoginRequest": { + "type": "object", + "properties": { + "password": { + "type": "string", + "example": "testme" + }, + "user_name": { + "type": "string", + "example": "admin0" + } + } + }, + "learnsteam_cslms-api_internal_models.LoginResponse": { + "type": "object", + "properties": { + "refresh_token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDc4OTcwMjcs" + }, + "token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDI3MTMwMjcsInN1" + }, + "user": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.User" + } + } + }, + "learnsteam_cslms-api_internal_models.Quiz": { + "type": "object", + "properties": { + "answer": { + "type": "array", + "items": { + "type": "integer" + } + }, + "center_id": { + "type": "integer", + "example": 100001 + }, + "content": { + "type": "array", + "items": { + "type": "integer" + } + }, + "guid_id": { + "type": "string", + "example": "2036023a-fb56-4b6c-b3bb-c787c681ada6" + }, + "id": { + "type": "integer", + "example": 1000001 + }, + "question": { + "type": "string", + "example": "퀴즈 질문입니다." + }, + "question_type": { + "type": "string", + "example": "choice" + }, + "quiz_paper_id": { + "type": "integer", + "example": 100001 + }, + "vol_no": { + "type": "integer", + "example": 5 + } + } + }, + "learnsteam_cslms-api_internal_models.QuizContent": { + "type": "object", + "properties": { + "hint": { + "type": "string", + "example": "markdown문서로 작성됨" + }, + "option1": { + "type": "string", + "example": "markdown 문서로 작성됨" + }, + "option2": { + "type": "string", + "example": "markdown 문서로 작성됨" + }, + "option3": { + "type": "string", + "example": "markdown 문서로 작성됨" + }, + "option4": { + "type": "string", + "example": "markdown 문서로 작성됨" + } + } + }, + "learnsteam_cslms-api_internal_models.QuizListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.Quiz" + } + }, + "page": { + "type": "integer", + "example": 1 + }, + "pageSize": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 5 + }, + "totalPage": { + "type": "integer", + "example": 1 + } + } + }, + "learnsteam_cslms-api_internal_models.QuizPaper": { + "type": "object", + "properties": { + "category": { + "type": "string", + "example": "파이썬기본" + }, + "center_id": { + "type": "integer", + "example": 100001 + }, + "content": { + "type": "string", + "example": "퀴즈 시트 설명" + }, + "guid_id": { + "type": "string", + "example": "ef74c59a-c707-4162-a52b-455906c81ec1" + }, + "id": { + "type": "integer", + "example": 100001 + }, + "manager_id": { + "type": "integer", + "example": 100001 + }, + "status": { + "type": "string", + "example": "on" + }, + "tag": { + "type": "array", + "items": { + "type": "integer" + } + }, + "title": { + "type": "string", + "example": "퀴즈 시트 제목" + } + } + }, + "learnsteam_cslms-api_internal_models.QuizPaperCopyRequest": { + "type": "object", + "properties": { + "center_id": { + "type": "integer", + "example": 100002 + }, + "title": { + "type": "string", + "example": "퀴즈 시트 제목" + } + } + }, + "learnsteam_cslms-api_internal_models.QuizPaperCopyResponse": { + "type": "object", + "properties": { + "quiz": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.Quiz" + } + }, + "quiz_paper": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaper" + } + } + }, + "learnsteam_cslms-api_internal_models.QuizPaperListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizPaper" + } + }, + "page": { + "type": "integer", + "example": 1 + }, + "pageSize": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 999 + }, + "totalPage": { + "type": "integer", + "example": 99 + } + } + }, + "learnsteam_cslms-api_internal_models.QuizPaperRequest": { + "type": "object", + "properties": { + "category": { + "type": "string", + "example": "파이썬기본" + }, + "center_id": { + "type": "integer", + "example": 100001 + }, + "content": { + "type": "string", + "example": "퀴즈 시트 설명" + }, + "status": { + "type": "string", + "example": "on" + }, + "tag": { + "type": "array", + "items": { + "type": "integer" + } + }, + "title": { + "type": "string", + "example": "퀴즈 시트 제목" + } + } + }, + "learnsteam_cslms-api_internal_models.QuizPaperResponse": { + "type": "object", + "properties": { + "category": { + "type": "string", + "example": "파이썬기본" + }, + "center_id": { + "type": "integer", + "example": 100001 + }, + "content": { + "type": "string", + "example": "퀴즈 시트 설명" + }, + "id": { + "type": "integer", + "example": 100001 + }, + "manager_id": { + "type": "integer", + "example": 100001 + }, + "status": { + "type": "string", + "example": "on" + }, + "tag": { + "type": "array", + "items": { + "type": "integer" + } + }, + "title": { + "type": "string", + "example": "퀴즈 시트 제목" + } + } + }, + "learnsteam_cslms-api_internal_models.QuizRequest": { + "type": "object", + "properties": { + "answer": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "option1", + "option2" + ] + }, + "center_id": { + "type": "integer", + "example": 1000001 + }, + "content": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizContent" + }, + "question": { + "type": "string", + "example": "질문입니다." + }, + "question_type": { + "type": "string", + "example": "choice" + }, + "quiz_paper_id": { + "type": "integer", + "example": 1000001 + }, + "vol_no": { + "type": "integer", + "example": 1 + } + } + }, + "learnsteam_cslms-api_internal_models.QuizResponse": { + "type": "object", + "properties": { + "answer": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "option1", + "option2" + ] + }, + "content": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizContent" + }, + "id": { + "type": "integer", + "example": 1000001 + }, + "question": { + "type": "string", + "example": "퀴즈 질문입니다." + }, + "question_type": { + "type": "string", + "example": "check" + }, + "quiz_paper_id": { + "type": "integer", + "example": 1000001 + }, + "vol_no": { + "type": "integer", + "example": 5 + } + } + }, + "learnsteam_cslms-api_internal_models.RegisterRequest": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "example": "길순" + }, + "gender": { + "type": "string", + "example": "F" + }, + "last_name": { + "type": "string", + "example": "홍" + }, + "password": { + "type": "string", + "example": "StrongPass!@#$" + }, + "phone_cs": { + "type": "string", + "example": "01012345678" + }, + "upload_image": { + "type": "string", + "example": "" + }, + "user_name": { + "type": "string", + "example": "gilsoon" + }, + "user_role": { + "type": "string", + "example": "member" + } + } + }, + "learnsteam_cslms-api_internal_models.RegisterResponse": { + "type": "object", + "properties": { + "refresh_token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDc4OTcwMjcs" + }, + "token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDI3MTMwMjcsInN1" + }, + "user": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.User" + } + } + }, + "learnsteam_cslms-api_internal_models.Token": { + "type": "object", + "properties": { + "ending_at": { + "type": "string" + }, + "refresh_token": { + "type": "string" + }, + "register_at": { + "type": "string" + }, + "status": { + "type": "string" + }, + "token": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "learnsteam_cslms-api_internal_models.User": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "example": "길순" + }, + "gender": { + "type": "string", + "example": "F" + }, + "guid_id": { + "type": "string", + "example": "137c1683-2ad6-4201-b256-253828b61c49" + }, + "id": { + "type": "integer", + "example": 100001 + }, + "last_name": { + "type": "string", + "example": "홍" + }, + "memo_cs": { + "type": "string", + "example": "사용자 메모" + }, + "phone_cs": { + "type": "string", + "example": "010-1234-5678" + }, + "register_at": { + "type": "string" + }, + "upload_image": { + "type": "string", + "example": "image_url" + }, + "user_name": { + "type": "string", + "example": "user0" + }, + "user_role": { + "type": "string", + "example": "member" + } + } + }, + "learnsteam_cslms-api_internal_models.UserListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.User" + } + }, + "page": { + "type": "integer", + "example": 1 + }, + "pageSize": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 90 + }, + "totalPage": { + "type": "integer", + "example": 9 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizResponse" + } + }, + "page": { + "type": "integer", + "example": 1 + }, + "pageSize": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 5 + }, + "totalPage": { + "type": "integer", + "example": 1 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizPaper": { + "type": "object", + "properties": { + "center_id": { + "type": "integer", + "example": 1000015 + }, + "done_at": { + "type": "string" + }, + "guid_id": { + "type": "string", + "example": "7f9329f5-2e36-4638-92d2-73064b7291a4" + }, + "id": { + "type": "integer", + "example": 1000015 + }, + "quiz_paper_id": { + "type": "integer", + "example": 1000001 + }, + "start_at": { + "type": "string" + }, + "status": { + "type": "string", + "example": "wating" + }, + "total_score": { + "type": "number", + "example": 100 + }, + "user_id": { + "type": "integer", + "example": 1000002 + }, + "user_score": { + "type": "number", + "example": 5 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizPaperListResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaper" + } + }, + "page": { + "type": "integer", + "example": 1 + }, + "pageSize": { + "type": "integer", + "example": 10 + }, + "total": { + "type": "integer", + "example": 999 + }, + "totalPage": { + "type": "integer", + "example": 99 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizPaperPatchRequest": { + "type": "object", + "properties": { + "done_at": { + "type": "string", + "example": "2023-11-10T13:25:00+09:00" + }, + "start_at": { + "type": "string", + "example": "2023-11-10T13:10:00+09:00" + }, + "status": { + "type": "string", + "example": "ready" + }, + "total_score": { + "type": "number", + "example": 80 + }, + "user_score": { + "type": "number", + "example": 4 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizPaperRequest": { + "type": "object", + "properties": { + "quiz_paper_id": { + "type": "integer", + "example": 1000002 + }, + "users": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizPaperResponse": { + "type": "object", + "properties": { + "center_id": { + "type": "integer", + "example": 1000015 + }, + "done_at": { + "type": "string" + }, + "guid_id": { + "type": "string", + "example": "7f9329f5-2e36-4638-92d2-73064b7291a4" + }, + "id": { + "type": "integer", + "example": 1000015 + }, + "quiz_paper_id": { + "type": "integer", + "example": 1000001 + }, + "start_at": { + "type": "string" + }, + "status": { + "type": "string", + "example": "wating" + }, + "total_score": { + "type": "number", + "example": 100 + }, + "user_id": { + "type": "integer", + "example": 1000002 + }, + "user_score": { + "type": "number", + "example": 5 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizPaperUpdateRequest": { + "type": "object", + "properties": { + "done_at": { + "type": "string", + "example": "2023-11-10T13:25:00+09:00" + }, + "quiz_paper_id": { + "type": "integer", + "example": 1000002 + }, + "start_at": { + "type": "string", + "example": "2023-11-10T13:10:00+09:00" + }, + "status": { + "type": "string", + "example": "ready" + }, + "total_score": { + "type": "number", + "example": 100 + }, + "user_id": { + "type": "integer", + "example": 1000003 + }, + "user_score": { + "type": "number", + "example": 5 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizRequest": { + "type": "object", + "properties": { + "answer": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "option1", + "option2" + ] + }, + "center_id": { + "type": "integer", + "example": 1000001 + }, + "content": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizContent" + }, + "question": { + "type": "string", + "example": "질문입니다." + }, + "question_type": { + "type": "string", + "example": "choice" + }, + "quiz_paper_id": { + "type": "integer", + "example": 1000001 + }, + "result": { + "type": "string", + "example": "success" + }, + "score": { + "type": "number", + "example": 10 + }, + "status": { + "type": "string", + "example": "waiting" + }, + "user_id": { + "type": "integer", + "example": 1000001 + }, + "vol_no": { + "type": "integer", + "example": 1 + } + } + }, + "learnsteam_cslms-api_internal_models.UserQuizResponse": { + "type": "object", + "properties": { + "answer": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "option1", + "option2" + ] + }, + "center_id": { + "type": "string", + "example": "2036023a-fb56-4b6c-b3bb-c787c681ada6" + }, + "content": { + "$ref": "#/definitions/learnsteam_cslms-api_internal_models.QuizContent" + }, + "guid_id": { + "type": "string", + "example": "2036023a-fb56-4b6c-b3bb-c787c681ada6" + }, + "id": { + "type": "integer", + "example": 1000001 + }, + "question": { + "type": "string", + "example": "퀴즈 질문입니다." + }, + "question_type": { + "type": "string", + "example": "check" + }, + "result": { + "type": "string", + "example": "success" + }, + "score": { + "type": "number", + "example": 10 + }, + "status": { + "type": "string", + "example": "waiting" + }, + "user_id": { + "type": "integer", + "example": 1000001 + }, + "user_quiz_paper_id": { + "type": "integer", + "example": 1000001 + }, + "vol_no": { + "type": "integer", + "example": 5 + } + } + } + }, + "securityDefinitions": { + "Bearer": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..ff49d8a --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,1381 @@ +definitions: + learnsteam_cslms-api_internal_models.Center: + properties: + center_name: + example: learnsteam_kd + type: string + center_title: + example: 강동런스팀로봇코딩학원 + type: string + company_info: + example: 사업자정보-json 기타 정보 추가 가능 + type: string + content_page: + example: 학원상세페이지, html/마크다운/text + type: string + guid_id: + example: 2036023a-fb56-4b6c-b3bb-c787c681ada6 + type: string + id: + example: 100001 + type: integer + memo: + example: 메모 + type: string + owner_id: + example: 100001 + type: integer + status: + example: "on" + type: string + type: object + learnsteam_cslms-api_internal_models.CenterListResponse: + properties: + data: + items: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.Center' + type: array + page: + example: 1 + type: integer + pageSize: + example: 10 + type: integer + total: + example: 5 + type: integer + totalPage: + example: 1 + type: integer + type: object + learnsteam_cslms-api_internal_models.CenterRequest: + properties: + center_name: + example: learnsteam_kd + type: string + center_title: + example: 강동런스팀로봇코딩학원 + type: string + company_info: + example: 사업자정보-json 기타 정보 추가 가능 + type: string + content_page: + example: 학원상세페이지, html/마크다운/text + type: string + memo: + example: 메모 + type: string + owner_id: + example: 100001 + type: integer + status: + example: "on" + type: string + type: object + learnsteam_cslms-api_internal_models.LoginRequest: + properties: + password: + example: testme + type: string + user_name: + example: admin0 + type: string + type: object + learnsteam_cslms-api_internal_models.LoginResponse: + properties: + refresh_token: + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDc4OTcwMjcs + type: string + token: + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDI3MTMwMjcsInN1 + type: string + user: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.User' + type: object + learnsteam_cslms-api_internal_models.Quiz: + properties: + answer: + items: + type: integer + type: array + center_id: + example: 100001 + type: integer + content: + items: + type: integer + type: array + guid_id: + example: 2036023a-fb56-4b6c-b3bb-c787c681ada6 + type: string + id: + example: 1000001 + type: integer + question: + example: 퀴즈 질문입니다. + type: string + question_type: + example: choice + type: string + quiz_paper_id: + example: 100001 + type: integer + vol_no: + example: 5 + type: integer + type: object + learnsteam_cslms-api_internal_models.QuizContent: + properties: + hint: + example: markdown문서로 작성됨 + type: string + option1: + example: markdown 문서로 작성됨 + type: string + option2: + example: markdown 문서로 작성됨 + type: string + option3: + example: markdown 문서로 작성됨 + type: string + option4: + example: markdown 문서로 작성됨 + type: string + type: object + learnsteam_cslms-api_internal_models.QuizListResponse: + properties: + data: + items: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.Quiz' + type: array + page: + example: 1 + type: integer + pageSize: + example: 10 + type: integer + total: + example: 5 + type: integer + totalPage: + example: 1 + type: integer + type: object + learnsteam_cslms-api_internal_models.QuizPaper: + properties: + category: + example: 파이썬기본 + type: string + center_id: + example: 100001 + type: integer + content: + example: 퀴즈 시트 설명 + type: string + guid_id: + example: ef74c59a-c707-4162-a52b-455906c81ec1 + type: string + id: + example: 100001 + type: integer + manager_id: + example: 100001 + type: integer + status: + example: "on" + type: string + tag: + items: + type: integer + type: array + title: + example: 퀴즈 시트 제목 + type: string + type: object + learnsteam_cslms-api_internal_models.QuizPaperCopyRequest: + properties: + center_id: + example: 100002 + type: integer + title: + example: 퀴즈 시트 제목 + type: string + type: object + learnsteam_cslms-api_internal_models.QuizPaperCopyResponse: + properties: + quiz: + items: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.Quiz' + type: array + quiz_paper: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizPaper' + type: object + learnsteam_cslms-api_internal_models.QuizPaperListResponse: + properties: + data: + items: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizPaper' + type: array + page: + example: 1 + type: integer + pageSize: + example: 10 + type: integer + total: + example: 999 + type: integer + totalPage: + example: 99 + type: integer + type: object + learnsteam_cslms-api_internal_models.QuizPaperRequest: + properties: + category: + example: 파이썬기본 + type: string + center_id: + example: 100001 + type: integer + content: + example: 퀴즈 시트 설명 + type: string + status: + example: "on" + type: string + tag: + items: + type: integer + type: array + title: + example: 퀴즈 시트 제목 + type: string + type: object + learnsteam_cslms-api_internal_models.QuizPaperResponse: + properties: + category: + example: 파이썬기본 + type: string + center_id: + example: 100001 + type: integer + content: + example: 퀴즈 시트 설명 + type: string + id: + example: 100001 + type: integer + manager_id: + example: 100001 + type: integer + status: + example: "on" + type: string + tag: + items: + type: integer + type: array + title: + example: 퀴즈 시트 제목 + type: string + type: object + learnsteam_cslms-api_internal_models.QuizRequest: + properties: + answer: + example: + - option1 + - option2 + items: + type: string + type: array + center_id: + example: 1000001 + type: integer + content: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizContent' + question: + example: 질문입니다. + type: string + question_type: + example: choice + type: string + quiz_paper_id: + example: 1000001 + type: integer + vol_no: + example: 1 + type: integer + type: object + learnsteam_cslms-api_internal_models.QuizResponse: + properties: + answer: + example: + - option1 + - option2 + items: + type: string + type: array + content: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizContent' + id: + example: 1000001 + type: integer + question: + example: 퀴즈 질문입니다. + type: string + question_type: + example: check + type: string + quiz_paper_id: + example: 1000001 + type: integer + vol_no: + example: 5 + type: integer + type: object + learnsteam_cslms-api_internal_models.RegisterRequest: + properties: + first_name: + example: 길순 + type: string + gender: + example: F + type: string + last_name: + example: 홍 + type: string + password: + example: StrongPass!@#$ + type: string + phone_cs: + example: "01012345678" + type: string + upload_image: + example: "" + type: string + user_name: + example: gilsoon + type: string + user_role: + example: member + type: string + type: object + learnsteam_cslms-api_internal_models.RegisterResponse: + properties: + refresh_token: + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDc4OTcwMjcs + type: string + token: + example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDI3MTMwMjcsInN1 + type: string + user: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.User' + type: object + learnsteam_cslms-api_internal_models.Token: + properties: + ending_at: + type: string + refresh_token: + type: string + register_at: + type: string + status: + type: string + token: + type: string + user_id: + type: integer + type: object + learnsteam_cslms-api_internal_models.User: + properties: + first_name: + example: 길순 + type: string + gender: + example: F + type: string + guid_id: + example: 137c1683-2ad6-4201-b256-253828b61c49 + type: string + id: + example: 100001 + type: integer + last_name: + example: 홍 + type: string + memo_cs: + example: 사용자 메모 + type: string + phone_cs: + example: 010-1234-5678 + type: string + register_at: + type: string + upload_image: + example: image_url + type: string + user_name: + example: user0 + type: string + user_role: + example: member + type: string + type: object + learnsteam_cslms-api_internal_models.UserListResponse: + properties: + data: + items: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.User' + type: array + page: + example: 1 + type: integer + pageSize: + example: 10 + type: integer + total: + example: 90 + type: integer + totalPage: + example: 9 + type: integer + type: object + learnsteam_cslms-api_internal_models.UserQuizListResponse: + properties: + data: + items: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizResponse' + type: array + page: + example: 1 + type: integer + pageSize: + example: 10 + type: integer + total: + example: 5 + type: integer + totalPage: + example: 1 + type: integer + type: object + learnsteam_cslms-api_internal_models.UserQuizPaper: + properties: + center_id: + example: 1000015 + type: integer + done_at: + type: string + guid_id: + example: 7f9329f5-2e36-4638-92d2-73064b7291a4 + type: string + id: + example: 1000015 + type: integer + quiz_paper_id: + example: 1000001 + type: integer + start_at: + type: string + status: + example: wating + type: string + total_score: + example: 100 + type: number + user_id: + example: 1000002 + type: integer + user_score: + example: 5 + type: number + type: object + learnsteam_cslms-api_internal_models.UserQuizPaperListResponse: + properties: + data: + items: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaper' + type: array + page: + example: 1 + type: integer + pageSize: + example: 10 + type: integer + total: + example: 999 + type: integer + totalPage: + example: 99 + type: integer + type: object + learnsteam_cslms-api_internal_models.UserQuizPaperPatchRequest: + properties: + done_at: + example: "2023-11-10T13:25:00+09:00" + type: string + start_at: + example: "2023-11-10T13:10:00+09:00" + type: string + status: + example: ready + type: string + total_score: + example: 80 + type: number + user_score: + example: 4 + type: number + type: object + learnsteam_cslms-api_internal_models.UserQuizPaperRequest: + properties: + quiz_paper_id: + example: 1000002 + type: integer + users: + items: + type: integer + type: array + type: object + learnsteam_cslms-api_internal_models.UserQuizPaperResponse: + properties: + center_id: + example: 1000015 + type: integer + done_at: + type: string + guid_id: + example: 7f9329f5-2e36-4638-92d2-73064b7291a4 + type: string + id: + example: 1000015 + type: integer + quiz_paper_id: + example: 1000001 + type: integer + start_at: + type: string + status: + example: wating + type: string + total_score: + example: 100 + type: number + user_id: + example: 1000002 + type: integer + user_score: + example: 5 + type: number + type: object + learnsteam_cslms-api_internal_models.UserQuizPaperUpdateRequest: + properties: + done_at: + example: "2023-11-10T13:25:00+09:00" + type: string + quiz_paper_id: + example: 1000002 + type: integer + start_at: + example: "2023-11-10T13:10:00+09:00" + type: string + status: + example: ready + type: string + total_score: + example: 100 + type: number + user_id: + example: 1000003 + type: integer + user_score: + example: 5 + type: number + type: object + learnsteam_cslms-api_internal_models.UserQuizRequest: + properties: + answer: + example: + - option1 + - option2 + items: + type: string + type: array + center_id: + example: 1000001 + type: integer + content: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizContent' + question: + example: 질문입니다. + type: string + question_type: + example: choice + type: string + quiz_paper_id: + example: 1000001 + type: integer + result: + example: success + type: string + score: + example: 10 + type: number + status: + example: waiting + type: string + user_id: + example: 1000001 + type: integer + vol_no: + example: 1 + type: integer + type: object + learnsteam_cslms-api_internal_models.UserQuizResponse: + properties: + answer: + example: + - option1 + - option2 + items: + type: string + type: array + center_id: + example: 2036023a-fb56-4b6c-b3bb-c787c681ada6 + type: string + content: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizContent' + guid_id: + example: 2036023a-fb56-4b6c-b3bb-c787c681ada6 + type: string + id: + example: 1000001 + type: integer + question: + example: 퀴즈 질문입니다. + type: string + question_type: + example: check + type: string + result: + example: success + type: string + score: + example: 10 + type: number + status: + example: waiting + type: string + user_id: + example: 1000001 + type: integer + user_quiz_paper_id: + example: 1000001 + type: integer + vol_no: + example: 5 + type: integer + type: object +info: + contact: + email: sheen@jongyeob.com + name: Jay Sheen + description: Learnsteam CodingSchool API + title: Learnsteam CodingSchool API + version: "1.0" +paths: + /auth/login: + post: + consumes: + - application/json + description: 사용자가 로그인합니다. + parameters: + - description: Login Body + in: body + name: loginBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.LoginRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.LoginResponse' + summary: 사용자 로그인 로그인 + tags: + - 로그인 + /auth/register: + post: + consumes: + - application/json + description: 회원가입 + parameters: + - description: Register Body + in: body + name: registerBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.RegisterRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.RegisterResponse' + summary: 회원가입 + tags: + - 회원가입 + /center: + get: + consumes: + - application/json + description: 센터 목록을 가져옵니다. + parameters: + - description: 검색어 + in: query + name: q + type: string + - description: 페이지 + in: query + name: page + type: integer + - description: 페이지 사이즈 + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.CenterListResponse' + security: + - Bearer: [] + summary: 센터 목록 가져오기 + tags: + - 센터 + /center/{id}: + get: + consumes: + - application/json + description: ID로 센터 정보를 가져옵니다. + parameters: + - description: 센터 ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.Center' + security: + - Bearer: [] + summary: 센터 정보 가져오기 + tags: + - 센터 + put: + consumes: + - application/json + description: 센터를 수정합니다. + parameters: + - description: Center ID + in: path + name: id + required: true + type: integer + - description: Center Body + in: body + name: centerBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.CenterRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.Center' + security: + - Bearer: [] + summary: 센터 수정 + tags: + - 센터 + /quiz: + get: + consumes: + - application/json + description: 퀴즈 목록을 가져옵니다. + parameters: + - description: 퀴즈페이퍼 ID + in: query + name: quiz_paper_id + required: true + type: string + - description: 검색어 + in: query + name: q + type: string + - description: 페이지 + in: query + name: page + type: integer + - description: 페이지 사이즈 + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizListResponse' + security: + - Bearer: [] + summary: 퀴즈 목록 가져오기 + tags: + - 퀴즈 + post: + consumes: + - application/json + description: 퀴즈를 만듭니다. + parameters: + - description: Quiz Body + in: body + name: quizBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizResponse' + security: + - Bearer: [] + summary: 퀴즈 생성 + tags: + - 퀴즈 + /quiz/{id}: + get: + consumes: + - application/json + description: ID로 퀴즈를 가져옵니다. + parameters: + - description: 퀴즈 ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizResponse' + security: + - Bearer: [] + summary: 퀴즈 가져오기 + tags: + - 퀴즈 + put: + consumes: + - application/json + description: 퀴즈를 수정합니다. + parameters: + - description: Quiz ID + in: path + name: id + required: true + type: integer + - description: Quiz Body + in: body + name: quizBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizResponse' + security: + - Bearer: [] + summary: 퀴즈 수정 + tags: + - 퀴즈 + /quizpaper: + get: + consumes: + - application/json + description: 퀴즈페이퍼 목록을 가져옵니다. + parameters: + - description: 태그 + in: query + name: tag + type: string + - description: 검색어 + in: query + name: q + type: string + - description: 페이지 + in: query + name: page + type: integer + - description: 페이지 사이즈 + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizPaperListResponse' + security: + - Bearer: [] + summary: 퀴즈페이퍼 목록 가져오기 + tags: + - 퀴즈페이퍼 + post: + consumes: + - application/json + description: 퀴즈 퀴즈페이퍼을 만듭니다. + parameters: + - description: QuizPaper Body + in: body + name: quizPaperBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizPaperRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizPaperResponse' + security: + - Bearer: [] + summary: 퀴즈 퀴즈페이퍼 생성 + tags: + - 퀴즈페이퍼 + /quizpaper/{id}: + get: + consumes: + - application/json + description: ID로 퀴즈 퀴즈페이퍼을 가져옵니다. + parameters: + - description: 퀴즈 퀴즈페이퍼 ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizPaperResponse' + security: + - Bearer: [] + summary: 퀴즈 퀴즈페이퍼 가져오기 + tags: + - 퀴즈페이퍼 + put: + consumes: + - application/json + description: 퀴즈 퀴즈페이퍼을 수정합니다. + parameters: + - description: 퀴즈 퀴즈페이퍼 ID + in: path + name: id + required: true + type: string + - description: QuizPaper Body + in: body + name: quizPaperUpdateBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizPaperRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizPaperResponse' + security: + - Bearer: [] + summary: 퀴즈 퀴즈페이퍼 수정 + tags: + - 퀴즈페이퍼 + /quizpaper/{id}/copy: + post: + consumes: + - application/json + description: 퀴즈 퀴즈페이퍼와 퀴즈를 복사합니다. + parameters: + - description: 퀴즈페이퍼 ID + in: path + name: id + required: true + type: string + - description: QuizPaper Copy Body + in: body + name: quizPaperCopyBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizPaperCopyRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.QuizPaperCopyResponse' + security: + - Bearer: [] + summary: 퀴즈 퀴즈페이퍼 복사 + tags: + - 퀴즈페이퍼 + /token/refresh: + post: + consumes: + - application/json + description: AccessToken을 RefreshToken으로 갱신합니다. + parameters: + - description: RefreshToken + in: body + name: refresh_token + required: true + schema: + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.Token' + summary: AccessToken Refresh + tags: + - Token + /user: + get: + consumes: + - application/json + description: 사용자 목록을 가져옵니다. + parameters: + - description: 검색어 + in: query + name: q + type: string + - description: 페이지 + in: query + name: page + type: integer + - description: 페이지 사이즈 + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserListResponse' + security: + - Bearer: [] + summary: 사용자 목록 가져오기 + tags: + - 사용자 + /user/{id}: + get: + consumes: + - application/json + description: ID로 사용자 정보를 가져옵니다. + parameters: + - description: 사용자 ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.User' + security: + - Bearer: [] + summary: 사용자 정보 가져오기 + tags: + - 사용자 + /userquiz: + get: + consumes: + - application/json + description: 사용자퀴즈 목록을 가져옵니다. + parameters: + - description: 사용자퀴즈페이퍼 ID + in: query + name: user_quiz_paper_id + required: true + type: string + - description: 검색어 + in: query + name: q + type: string + - description: 페이지 + in: query + name: page + type: integer + - description: 페이지 사이즈 + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizListResponse' + security: + - Bearer: [] + summary: 사용자퀴즈 목록 가져오기 + tags: + - 사용자퀴즈 + post: + consumes: + - application/json + description: 사용자퀴즈를 만듭니다. + parameters: + - description: UserQuiz Body + in: body + name: quizBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizResponse' + security: + - Bearer: [] + summary: 사용자퀴즈 생성 + tags: + - 사용자퀴즈 + /userquiz/{id}: + get: + consumes: + - application/json + description: ID로 사용자퀴즈를 가져옵니다. + parameters: + - description: 사용자퀴즈 ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizResponse' + security: + - Bearer: [] + summary: 사용자퀴즈 가져오기 + tags: + - 사용자퀴즈 + put: + consumes: + - application/json + description: 사용자퀴즈를 수정합니다. + parameters: + - description: Quiz ID + in: path + name: id + required: true + type: integer + - description: Quiz Body + in: body + name: quizBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizResponse' + security: + - Bearer: [] + summary: 사용자퀴즈 수정 + tags: + - 사용자퀴즈 + /userquizpaper: + get: + consumes: + - application/json + description: 응시 목록을 가져옵니다. + parameters: + - description: 검색어 + in: query + name: q + type: string + - description: 페이지 + in: query + name: page + type: integer + - description: 페이지 사이즈 + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperListResponse' + security: + - Bearer: [] + summary: 응시 목록 가져오기 + tags: + - 사용자퀴즈페이퍼 + post: + consumes: + - application/json + description: 응시 매칭을 만듭니다. + parameters: + - description: UserQuizPaper Body + in: body + name: userQuizPaperBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaper' + type: array + security: + - Bearer: [] + summary: 응시 매칭 생성 + tags: + - 사용자퀴즈페이퍼 + /userquizpaper/{id}: + get: + consumes: + - application/json + description: ID로 응시 정보를 가져옵니다. + parameters: + - description: 응시 ID + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperResponse' + security: + - Bearer: [] + summary: 응시 정보 가져오기 + tags: + - 사용자퀴즈페이퍼 + patch: + consumes: + - application/json + description: 응시 정보를 변경합니다. + parameters: + - description: UserQuizPaper ID + in: path + name: id + required: true + type: integer + - description: UserQuizPaper Patch Body (변경할 필드만 입력) + in: body + name: userQuizPaperPatchBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperPatchRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperResponse' + security: + - Bearer: [] + summary: 응시 정보 변경 + tags: + - 사용자퀴즈페이퍼 + put: + consumes: + - application/json + description: 응시를 수정합니다. + parameters: + - description: UserQuizPaper ID + in: path + name: id + required: true + type: integer + - description: UserQuizPaper Update Body + in: body + name: userQuizPaperUpdateBody + required: true + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperUpdateRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/learnsteam_cslms-api_internal_models.UserQuizPaperResponse' + security: + - Bearer: [] + summary: 응시 수정 + tags: + - 사용자퀴즈페이퍼 +securityDefinitions: + Bearer: + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f479f8a --- /dev/null +++ b/go.mod @@ -0,0 +1,66 @@ +module learnsteam/cslms-api + +go 1.21 + +require ( + github.com/apex/gateway v1.1.2 + github.com/gin-contrib/cors v1.4.0 + github.com/golang-jwt/jwt/v5 v5.1.0 + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.8.4 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 + github.com/swaggo/swag v1.16.2 + golang.org/x/crypto v0.15.0 + gorm.io/datatypes v1.2.0 + gorm.io/driver/mysql v1.5.2 + gorm.io/gorm v1.25.5 +) + +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/bytedance/sonic v1.10.2 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect + github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.20.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/spec v0.20.9 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.16.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.6.0 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.15.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/sqlite v1.5.4 // indirect +) + +require ( + github.com/aws/aws-lambda-go v1.17.0 // indirect + github.com/gin-gonic/gin v1.9.1 + github.com/pkg/errors v0.9.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1bd1196 --- /dev/null +++ b/go.sum @@ -0,0 +1,250 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/apex/gateway v1.1.2 h1:OWyLov8eaau8YhkYKkRuOAYqiUhpBJalBR1o+3FzX+8= +github.com/apex/gateway v1.1.2/go.mod h1:AMTkVbz5u5Hvd6QOGhhg0JUrNgCcLVu3XNJOGntdoB4= +github.com/aws/aws-lambda-go v1.17.0 h1:Ogihmi8BnpmCNktKAGpNwSiILNNING1MiosnKUfU8m0= +github.com/aws/aws-lambda-go v1.17.0/go.mod h1:FEwgPLE6+8wcGBTe5cJN3JWurd1Ztm9zN4jsXsjzKKw= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= +github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= +github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= +github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= +github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= +github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= +github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= +github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU= +github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA= +github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= +github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= +github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= +github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= +github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= +github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= +golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco= +gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04= +gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= +gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U= +gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A= +gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= +gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= +gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= +gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= +gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/controllers/auth.go b/internal/controllers/auth.go new file mode 100644 index 0000000..d02298f --- /dev/null +++ b/internal/controllers/auth.go @@ -0,0 +1,122 @@ +package controllers + +import ( + "net/http" + + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" +) + +type AuthController interface { + Register(*gin.Context) + Login(*gin.Context) +} + +type authController struct { + service services.AuthService + tokenService services.TokenService +} + +func NewAuthController(service services.AuthService, tokenService services.TokenService) AuthController { + return &authController{ + service: service, + tokenService: tokenService, + } +} + +// Register +// +// @Summary 회원가입 +// @Description 회원가입 +// @Tags 회원가입 +// @Accept json +// @Produce json +// +// @Param registerBody body models.RegisterRequest true "Register Body" +// +// @Success 200 {object} models.RegisterResponse +// @Router /auth/register [post] +func (controller *authController) Register(c *gin.Context) { + var params models.RegisterRequest + if c.BindJSON(¶ms) != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) + return + } + + user, err := controller.service.Register(¶ms) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + token, err := controller.tokenService.Create(user.ID, user.UserRole) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"user": user, "token": token.Token, "refresh_token": token.RefreshToken}) +} + +// Login 사용자 로그인 +// +// @Summary 사용자 로그인 로그인 +// @Description 사용자가 로그인합니다. +// @Tags 로그인 +// +// @Accept json +// @Produce json +// +// @Param loginBody body models.LoginRequest true "Login Body" +// +// @Success 200 {object} models.LoginResponse +// @Router /auth/login [post] +func (controller *authController) Login(c *gin.Context) { + var request models.LoginRequest + if err := c.BindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user, err := controller.service.Login(&request) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + token, err := controller.tokenService.Create(user.ID, user.UserRole) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "user": user, + "token": token.Token, + "refresh_token": token.RefreshToken, + }) +} + +// Logout +func (controller *authController) Logout(c *gin.Context) { + token := c.GetHeader("Authorization") + if token == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) + return + } + + err := controller.tokenService.Delete(token) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.SetCookie("Authorization", "", -1, "/", "localhost", false, true) + c.SetCookie("RefreshToken", "", -1, "/", "localhost", false, true) + c.SetCookie("ExpiresAt", "", -1, "/", "localhost", false, true) + c.SetCookie("RefreshExpiresAt", "", -1, "/", "localhost", false, true) + c.SetCookie("RefreshTokenExpiresAt", "", -1, "/", "localhost", false, true) + c.SetCookie("RefreshTokenExpiresAt", "", -1, "/", "localhost", false, true) + c.SetCookie("RefreshTokenExpiresAt", "", -1, "/", "localhost", false, true) + c.JSON(http.StatusOK, gin.H{"message": "logout"}) +} diff --git a/internal/controllers/center.go b/internal/controllers/center.go new file mode 100644 index 0000000..786626d --- /dev/null +++ b/internal/controllers/center.go @@ -0,0 +1,220 @@ +package controllers + +import ( + "fmt" + "net/http" + "strconv" + "time" + + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +type CenterController interface { + List(*gin.Context) + Find(*gin.Context) + Create(*gin.Context) + Update(*gin.Context) +} + +type centerController struct { + service services.CenterService + tokenService services.TokenService +} + +func NewCenterController(service services.CenterService, tokenService services.TokenService) CenterController { + return ¢erController{ + service: service, + tokenService: tokenService, + } +} + +// Center List +// +// @Summary 센터 목록 가져오기 +// @Description 센터 목록을 가져옵니다. +// @Tags 센터 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param q query string false "검색어" +// @Param page query int false "페이지" +// @Param limit query int false "페이지 사이즈" +// +// @Success 200 {object} models.CenterListResponse +// @Router /center [get] +func (controller *centerController) 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.CenterListResponse{ + Data: *result, + Total: totalPage, + Page: page, + TotalPage: totalPage, + PageSize: limit, + } + + c.JSON(http.StatusOK, response) + + c.JSON(http.StatusOK, response) +} + +// Get Center +// +// @Summary 센터 정보 가져오기 +// @Description ID로 센터 정보를 가져옵니다. +// @Tags 센터 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path string true "센터 ID" +// +// @Success 200 {object} models.Center +// @Router /center/{id} [get] +func (controller *centerController) Find(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + 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 Center +// +// @Summary 센터 생성 +// @Description 센터를 만듭니다. +// @Tags 센터 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param quizBody body models.CenterRequest true "Center Body" +// +// @Success 200 {object} models.Center +// @Router /quiz [post] +func (controller *centerController) Create(c *gin.Context) { + var request models.CenterRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + center := &models.Center{ + GUID: uuid.NewString(), + Title: request.Title, + Name: request.Name, + OwnerID: request.OwnerID, + Content: request.Content, + Info: request.Info, + Memo: request.Memo, + Status: request.Status, + UpdatedAt: time.Now(), + CreatedAt: time.Now(), + } + + result, err := controller.service.Create(center) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) +} + +// Update Center +// +// @Summary 센터 수정 +// @Description 센터를 수정합니다. +// @Tags 센터 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path int64 true "Center ID" +// @Param centerBody body models.CenterRequest true "Center Body" +// +// @Success 200 {object} models.Center +// @Router /center/{id} [put] +func (controller *centerController) Update(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var request models.CenterRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + center, err := controller.service.Find(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + center.OwnerID = request.OwnerID + center.Title = request.Title + center.Name = request.Name + center.Content = request.Content + center.Info = request.Info + center.Memo = request.Memo + center.Status = request.Status + center.UpdatedAt = time.Now() + + result, err := controller.service.Update(center) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) +} diff --git a/internal/controllers/quiz.go b/internal/controllers/quiz.go new file mode 100644 index 0000000..493e5dd --- /dev/null +++ b/internal/controllers/quiz.go @@ -0,0 +1,243 @@ +package controllers + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "gorm.io/datatypes" +) + +type QuizController interface { + List(*gin.Context) + Find(*gin.Context) + Create(*gin.Context) + Update(*gin.Context) +} + +type quizController struct { + service services.QuizService +} + +func NewQuizController(service services.QuizService) QuizController { + return &quizController{ + service: service, + } +} + +// Quiz List +// +// @Summary 퀴즈 목록 가져오기 +// @Description 퀴즈 목록을 가져옵니다. +// @Tags 퀴즈 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param quiz_paper_id query string true "퀴즈페이퍼 ID" +// @Param q query string false "검색어" +// @Param page query int false "페이지" +// @Param limit query int false "페이지 사이즈" +// +// @Success 200 {object} models.QuizListResponse +// @Router /quiz [get] +func (controller *quizController) List(c *gin.Context) { + quiz_paper_id, err := strconv.ParseInt(c.DefaultQuery("quiz_paper_id", ""), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + 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(quiz_paper_id, page, limit) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + totalNumber, err := controller.service.Total(quiz_paper_id) + if err != nil { + fmt.Println("error : list") + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + totalPage := int64(totalNumber/int64(limit) + 1) + + c.JSON(http.StatusOK, gin.H{"data": result, "total": totalNumber, "page": page, "totalPage": totalPage, "pageSize": limit}) +} + +// Get Quiz +// +// @Summary 퀴즈 가져오기 +// @Description ID로 퀴즈를 가져옵니다. +// @Tags 퀴즈 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path string true "퀴즈 ID" +// +// @Success 200 {object} models.QuizResponse +// @Router /quiz/{id} [get] +func (controller *quizController) Find(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + 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 Quiz +// +// @Summary 퀴즈 생성 +// @Description 퀴즈를 만듭니다. +// @Tags 퀴즈 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param quizBody body models.QuizRequest true "Quiz Body" +// +// @Success 200 {object} models.QuizResponse +// @Router /quiz [post] +func (controller *quizController) Create(c *gin.Context) { + var request models.QuizRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var content datatypes.JSON + content, err := json.Marshal(request.Content) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var answer datatypes.JSON + answer, err = json.Marshal(request.Answer) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + quiz := &models.Quiz{ + GUID: uuid.NewString(), + CenterID: request.CenterID, + QuizPaperID: request.QuizPaperID, + No: request.No, + QuestionType: request.QuestionType, + Question: request.Question, + Content: content, + Answer: answer, + UpdatedAt: time.Now(), + CreatedAt: time.Now(), + } + + result, err := controller.service.Create(quiz) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) +} + +// Update Quiz +// +// @Summary 퀴즈 수정 +// @Description 퀴즈를 수정합니다. +// @Tags 퀴즈 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path int64 true "Quiz ID" +// @Param quizBody body models.QuizRequest true "Quiz Body" +// +// @Success 200 {object} models.QuizResponse +// @Router /quiz/{id} [put] +func (controller *quizController) Update(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var request models.QuizRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + quiz, err := controller.service.Find(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var content datatypes.JSON + content, err = json.Marshal(request.Content) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var answer datatypes.JSON + answer, err = json.Marshal(request.Answer) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + quiz.QuizPaperID = request.QuizPaperID + quiz.No = request.No + quiz.QuestionType = request.QuestionType + quiz.Question = request.Question + quiz.Content = content + quiz.Answer = answer + quiz.UpdatedAt = time.Now() + + result, err := controller.service.Update(quiz) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) +} diff --git a/internal/controllers/quizpaper.go b/internal/controllers/quizpaper.go new file mode 100644 index 0000000..d9f6d78 --- /dev/null +++ b/internal/controllers/quizpaper.go @@ -0,0 +1,314 @@ +package controllers + +import ( + "fmt" + "net/http" + "strconv" + "time" + + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +type QuizPaperController interface { + List(*gin.Context) + Find(*gin.Context) + Create(*gin.Context) + Update(*gin.Context) + Copy(*gin.Context) +} + +type quizPaperController struct { + service services.QuizPaperService + quizService services.QuizService +} + +func NewQuizPaperController(service services.QuizPaperService, quizService services.QuizService) QuizPaperController { + return &quizPaperController{ + service: service, + quizService: quizService, + } +} + +// QuizPaper List +// +// @Summary 퀴즈페이퍼 목록 가져오기 +// @Description 퀴즈페이퍼 목록을 가져옵니다. +// @Tags 퀴즈페이퍼 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param tag query string false "태그" +// @Param q query string false "검색어" +// @Param page query int false "페이지" +// @Param limit query int false "페이지 사이즈" +// +// @Success 200 {object} models.QuizPaperListResponse +// @Router /quizpaper [get] +func (controller *quizPaperController) List(c *gin.Context) { + tag := c.DefaultQuery("tag", "") + 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, tag, page, limit) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + totalNumber, err := controller.service.Total(q, tag) + 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.QuizPaperListResponse{ + Data: *result, + Total: totalPage, + Page: page, + TotalPage: totalPage, + PageSize: limit, + } + + c.JSON(http.StatusOK, response) +} + +// Get quizPaper +// +// @Summary 퀴즈 퀴즈페이퍼 가져오기 +// @Description ID로 퀴즈 퀴즈페이퍼을 가져옵니다. +// @Tags 퀴즈페이퍼 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path string true "퀴즈 퀴즈페이퍼 ID" +// +// @Success 200 {object} models.QuizPaperResponse +// @Router /quizpaper/{id} [get] +func (controller *quizPaperController) Find(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + 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 QuizPaper +// +// @Summary 퀴즈 퀴즈페이퍼 생성 +// @Description 퀴즈 퀴즈페이퍼을 만듭니다. +// @Tags 퀴즈페이퍼 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param quizPaperBody body models.QuizPaperRequest true "QuizPaper Body" +// +// @Success 200 {object} models.QuizPaperResponse +// @Router /quizpaper [post] +func (controller *quizPaperController) Create(c *gin.Context) { + var request models.QuizPaperRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user_id := c.GetInt64("sub") + + quizPaper := &models.QuizPaper{ + GUID: uuid.NewString(), + CenterID: request.CenterID, + ManagerID: user_id, + Title: request.Title, + Content: request.Content, + Category: request.Category, + Tag: request.Tag, + Status: request.Status, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + result, err := controller.service.Create(quizPaper) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) +} + +// Update QuizPaper +// +// @Summary 퀴즈 퀴즈페이퍼 수정 +// @Description 퀴즈 퀴즈페이퍼을 수정합니다. +// @Tags 퀴즈페이퍼 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path string true "퀴즈 퀴즈페이퍼 ID" +// @Param quizPaperUpdateBody body models.QuizPaperRequest true "QuizPaper Body" +// +// @Success 200 {object} models.QuizPaperResponse +// @Router /quizpaper/{id} [put] +func (controller *quizPaperController) Update(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var request models.QuizPaperRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + quizPaper, err := controller.service.Find(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user_id := c.GetInt64("sub") + quizPaper.ManagerID = user_id + quizPaper.CenterID = request.CenterID + quizPaper.Title = request.Title + quizPaper.Content = request.Content + quizPaper.Category = request.Category + quizPaper.Tag = request.Tag + quizPaper.Status = request.Status + quizPaper.UpdatedAt = time.Now() + + result, err := controller.service.Update(quizPaper) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) +} + +// Copy QuizPaper +// +// @Summary 퀴즈 퀴즈페이퍼 복사 +// @Description 퀴즈 퀴즈페이퍼와 퀴즈를 복사합니다. +// @Tags 퀴즈페이퍼 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path string true "퀴즈페이퍼 ID" +// @Param quizPaperCopyBody body models.QuizPaperCopyRequest true "QuizPaper Copy Body" +// +// @Success 200 {object} models.QuizPaperCopyResponse +// @Router /quizpaper/{id}/copy [post] +func (controller *quizPaperController) Copy(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + fmt.Println("id : ", c.Param("id")) + if err != nil { + fmt.Println("id convert err") + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var request models.QuizPaperCopyRequest + if err := c.ShouldBindJSON(&request); err != nil { + fmt.Println("req err") + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user_id := c.GetInt64("sub") + template, err := controller.service.Find(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + newQuizPaper := &models.QuizPaper{ + GUID: uuid.NewString(), + CenterID: request.CenterID, + ManagerID: user_id, + Title: request.Title, + Content: template.Content, + Category: template.Category, + Tag: template.Tag, + Status: template.Status, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + quizPaper, err := controller.service.Create(newQuizPaper) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + quizTemplates, err := controller.quizService.List(id, 1, 10000) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // iterate quizTemplate + var quizzes []*models.Quiz + for _, quiz := range *quizTemplates { + newQuiz := &models.Quiz{ + GUID: uuid.NewString(), + CenterID: request.CenterID, + QuizPaperID: quizPaper.ID, + No: quiz.No, + QuestionType: quiz.QuestionType, + Question: quiz.Question, + Content: quiz.Content, + Answer: quiz.Answer, + UpdatedAt: time.Now(), + CreatedAt: time.Now(), + } + result, err := controller.quizService.Create(newQuiz) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + quizzes = append(quizzes, result) + } + + c.JSON(http.StatusOK, gin.H{"quiz_paper": quizPaper, "quiz": quizzes}) +} diff --git a/internal/controllers/swagger.go b/internal/controllers/swagger.go new file mode 100644 index 0000000..d8f7825 --- /dev/null +++ b/internal/controllers/swagger.go @@ -0,0 +1,22 @@ +package controllers + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +type SwaggerController interface { + Get(*gin.Context) +} + +type swaggerController struct { +} + +func NewSwaggerController() SwaggerController { + return &swaggerController{} +} + +func (controller *swaggerController) Get(c *gin.Context) { + c.Redirect(http.StatusFound, "/swagger/index.html") +} diff --git a/internal/controllers/token.go b/internal/controllers/token.go new file mode 100644 index 0000000..aa30d4b --- /dev/null +++ b/internal/controllers/token.go @@ -0,0 +1,53 @@ +package controllers + +import ( + "net/http" + + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" +) + +type TokenController interface { + Refresh(*gin.Context) +} + +type tokenController struct { + service services.TokenService +} + +func NewTokenController(service services.TokenService) TokenController { + return &tokenController{ + service: service, + } +} + +// Refresh Token +// +// @Summary AccessToken Refresh +// @Description AccessToken을 RefreshToken으로 갱신합니다. +// @Tags Token +// +// @Accept json +// @Produce json +// +// @Param refresh_token body string true "RefreshToken" +// +// @Router /token/refresh [post] +// @Success 200 {object} models.Token +func (controller *tokenController) Refresh(c *gin.Context) { + var request models.RefreshTokenRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + result, err := controller.service.Refresh(request.RefreshToken) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) +} diff --git a/internal/controllers/user.go b/internal/controllers/user.go new file mode 100644 index 0000000..7e9e73f --- /dev/null +++ b/internal/controllers/user.go @@ -0,0 +1,131 @@ +package controllers + +import ( + "fmt" + "net/http" + "strconv" + + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" +) + +type UserController interface { + List(*gin.Context) + Find(*gin.Context) +} + +type userController struct { + service services.UserService + tokenService services.TokenService +} + +func NewUserController(service services.UserService, tokenService services.TokenService) UserController { + return &userController{ + service: service, + tokenService: tokenService, + } +} + +// User List +// +// @Summary 사용자 목록 가져오기 +// @Description 사용자 목록을 가져옵니다. +// @Tags 사용자 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param q query string false "검색어" +// @Param page query int false "페이지" +// @Param limit query int false "페이지 사이즈" +// +// @Success 200 {object} models.UserListResponse +// @Router /user [get] +func (controller *userController) 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.UserListResponse{ + Data: *result, + Total: totalPage, + Page: page, + TotalPage: totalPage, + PageSize: limit, + } + + c.JSON(http.StatusOK, response) + + c.JSON(http.StatusOK, response) +} + +// Get User +// +// @Summary 사용자 정보 가져오기 +// @Description ID로 사용자 정보를 가져옵니다. +// @Tags 사용자 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path string true "사용자 ID" +// +// @Success 200 {object} models.User +// @Router /user/{id} [get] +func (controller *userController) Find(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + sub := c.GetString("sub") + user_id, err := strconv.ParseInt(sub, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if user_id != id { + c.JSON(http.StatusBadRequest, gin.H{"error": "Wrong user"}) + return + } + + result, err := controller.service.Find(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) +} diff --git a/internal/controllers/userquiz.go b/internal/controllers/userquiz.go new file mode 100644 index 0000000..be754c7 --- /dev/null +++ b/internal/controllers/userquiz.go @@ -0,0 +1,236 @@ +package controllers + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "gorm.io/datatypes" +) + +type UserQuizController interface { + List(*gin.Context) + Find(*gin.Context) + Create(*gin.Context) + Update(*gin.Context) +} + +type userQuizController struct { + service services.UserQuizService +} + +func NewUserQuizController(service services.UserQuizService) QuizController { + return &userQuizController{ + service: service, + } +} + +// UserQuiz List +// +// @Summary 사용자퀴즈 목록 가져오기 +// @Description 사용자퀴즈 목록을 가져옵니다. +// @Tags 사용자퀴즈 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param user_quiz_paper_id query string true "사용자퀴즈페이퍼 ID" +// @Param q query string false "검색어" +// @Param page query int false "페이지" +// @Param limit query int false "페이지 사이즈" +// +// @Success 200 {object} models.UserQuizListResponse +// @Router /userquiz [get] +func (controller *userQuizController) List(c *gin.Context) { + quiz_paper_id, err := strconv.ParseInt(c.DefaultQuery("user_quiz_paper_id", ""), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + 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(quiz_paper_id, page, limit) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + totalNumber, err := controller.service.Total(quiz_paper_id) + if err != nil { + fmt.Println("error : list") + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + totalPage := int64(totalNumber/int64(limit) + 1) + + c.JSON(http.StatusOK, gin.H{"data": result, "total": totalNumber, "page": page, "totalPage": totalPage, "pageSize": limit}) +} + +// Get UserQuiz +// +// @Summary 사용자퀴즈 가져오기 +// @Description ID로 사용자퀴즈를 가져옵니다. +// @Tags 사용자퀴즈 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path string true "사용자퀴즈 ID" +// +// @Success 200 {object} models.UserQuizResponse +// @Router /userquiz/{id} [get] +func (controller *userQuizController) Find(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + 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 Quiz +// +// @Summary 사용자퀴즈 생성 +// @Description 사용자퀴즈를 만듭니다. +// @Tags 사용자퀴즈 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param quizBody body models.UserQuizRequest true "UserQuiz Body" +// +// @Success 200 {object} models.UserQuizResponse +// @Router /userquiz [post] +func (controller *userQuizController) Create(c *gin.Context) { + var request models.UserQuizRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var content datatypes.JSON + content, err := json.Marshal(request.Content) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var answer datatypes.JSON + answer, err = json.Marshal([]string{}) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + quiz := &models.UserQuiz{ + GUID: uuid.NewString(), + CenterID: request.CenterID, + UserQuizPaperID: request.UserQuizPaperID, + No: request.No, + QuestionType: request.QuestionType, + Question: request.Question, + Content: content, + Answer: answer, + UpdatedAt: time.Now(), + CreatedAt: time.Now(), + } + + result, err := controller.service.Create(quiz) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) +} + +// Update Quiz +// +// @Summary 사용자퀴즈 수정 +// @Description 사용자퀴즈를 수정합니다. +// @Tags 사용자퀴즈 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path int64 true "Quiz ID" +// @Param quizBody body models.UserQuizRequest true "Quiz Body" +// +// @Success 200 {object} models.UserQuizResponse +// @Router /userquiz/{id} [put] +func (controller *userQuizController) Update(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var request models.UserQuizRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + userquiz, err := controller.service.Find(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // CenterID int64 `json:"center_id" example:"1000001"` + // UserQuizPaperID int64 `json:"quiz_paper_id" example:"1000001"` + // UserID int64 `json:"user_id" example:"1000001"` + // No int `json:"vol_no" example:"1"` + // QuestionType string `json:"question_type" example:"choice"` + // Question string `json:"question" example:"질문입니다."` + // Content datatypes.JSON `json:"content"` + // Answer datatypes.JSON `json:"answer"` + // Status string `json:"status" example:"waiting"` + // Result string `json:"result" example:"success"` + // Score float32 `json:"score" example:"10"` + + userquiz.CenterID = request.CenterID + userquiz.UserQuizPaperID = request.UserQuizPaperID + + result, err := controller.service.Update(userquiz) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, result) +} diff --git a/internal/controllers/userquizpaper.go b/internal/controllers/userquizpaper.go new file mode 100644 index 0000000..80af787 --- /dev/null +++ b/internal/controllers/userquizpaper.go @@ -0,0 +1,331 @@ +package controllers + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" +) + +type UserQuizPaperController interface { + List(*gin.Context) + Find(*gin.Context) + Create(*gin.Context) + Update(*gin.Context) + Patch(*gin.Context) +} + +type userQuizPaperController struct { + service services.UserQuizPaperService + userService services.UserService + quizPaperService services.QuizPaperService + userQuizService services.UserQuizService +} + +func NewUserQuizPaperController( + service services.UserQuizPaperService, + userService services.UserService, + quizPaperService services.QuizPaperService, + userQuizService services.UserQuizService, +) UserQuizPaperController { + return &userQuizPaperController{ + service: service, + userService: userService, + quizPaperService: quizPaperService, + userQuizService: userQuizService, + } +} + +// UserQuizPaper List +// +// @Summary 응시 목록 가져오기 +// @Description 응시 목록을 가져옵니다. +// @Tags 사용자퀴즈페이퍼 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param q query string false "검색어" +// @Param page query int false "페이지" +// @Param limit query int false "페이지 사이즈" +// +// @Success 200 {object} models.UserQuizPaperListResponse +// @Router /userquizpaper [get] +func (controller *userQuizPaperController) 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.UserQuizPaperListResponse{ + Data: *result, + Total: totalNumber, + Page: page, + TotalPage: totalPage, + PageSize: limit, + } + + c.JSON(http.StatusOK, response) +} + +// Get userQuizPaper +// +// @Summary 응시 정보 가져오기 +// @Description ID로 응시 정보를 가져옵니다. +// @Tags 사용자퀴즈페이퍼 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path string true "응시 ID" +// +// @Success 200 {object} models.UserQuizPaperResponse +// @Router /userquizpaper/{id} [get] +func (controller *userQuizPaperController) Find(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + 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 UserQuizPaper +// +// @Summary 응시 매칭 생성 +// @Description 응시 매칭을 만듭니다. +// @Tags 사용자퀴즈페이퍼 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param userQuizPaperBody body models.UserQuizPaperRequest true "UserQuizPaper Body" +// +// @Success 200 {object} []models.UserQuizPaper +// @Router /userquizpaper [post] +func (controller *userQuizPaperController) Create(c *gin.Context) { + var request models.UserQuizPaperRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + fmt.Println("users", request.Users) + + quizPaper, err := controller.quizPaperService.Find(request.QuizPaperID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var users []int64 + err = json.Unmarshal(request.Users, &users) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + templates := controller.service.Generate(users, quizPaper) + userQuizPapers, err := controller.service.Insert(templates) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var userquizzes []models.UserQuiz + for _, userQuizPaper := range userQuizPapers { + items, err := controller.userQuizService.Generate(request.QuizPaperID, &userQuizPaper) + if err == nil { + userquizzes = append(userquizzes, items...) + } + } + + _, err = controller.userQuizService.Insert(userquizzes) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"userQuizPaper": userQuizPapers}) +} + +// Update UserQuizPaper +// +// @Summary 응시 수정 +// @Description 응시를 수정합니다. +// @Tags 사용자퀴즈페이퍼 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path int64 true "UserQuizPaper ID" +// @Param userQuizPaperUpdateBody body models.UserQuizPaperUpdateRequest true "UserQuizPaper Update Body" +// +// @Success 200 {object} models.UserQuizPaperResponse +// @Router /userquizpaper/{id} [put] +func (controller *userQuizPaperController) Update(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var request models.UserQuizPaperUpdateRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + fmt.Println("request", request) + userQuizPaper, err := controller.service.Find(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + userQuizPaper.QuizPaperID = request.QuizPaperID + userQuizPaper.UserID = request.UserID + userQuizPaper.Status = request.Status + userQuizPaper.UserScore = request.UserScore + userQuizPaper.TotalScore = request.TotalScore + + 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 { + userQuizPaper.StartAt = &start_at + } + fmt.Println("start_at", start_at) + } + + if request.DoneAt != "" { + fmt.Println("request.EndAt", request.DoneAt) + done_at, err := time.Parse("2006-01-02T15:04:05-07:00", request.DoneAt) + if err == nil { + userQuizPaper.DoneAt = &done_at + } + fmt.Println("done_at", done_at) + } + fmt.Println("userQuizPaper", userQuizPaper) + result, err := controller.service.Update(userQuizPaper) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + fmt.Println("result", result) + c.JSON(http.StatusOK, result) +} + +// Patch UserQuizPaper +// +// @Summary 응시 정보 변경 +// @Description 응시 정보를 변경합니다. +// @Tags 사용자퀴즈페이퍼 +// +// @Accept json +// @Produce json +// +// @Security Bearer +// +// @Param id path int64 true "UserQuizPaper ID" +// @Param userQuizPaperPatchBody body models.UserQuizPaperPatchRequest true "UserQuizPaper Patch Body (변경할 필드만 입력)" +// +// @Success 200 {object} models.UserQuizPaperResponse +// @Router /userquizpaper/{id} [patch] +func (controller *userQuizPaperController) Patch(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var request models.UserQuizPaperPatchRequest + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + fmt.Println("request", request) + userQuizPaper, err := controller.service.Find(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if request.Status != "" { + userQuizPaper.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 { + userQuizPaper.StartAt = &start_at + } + fmt.Println("start_at", start_at) + } + + if request.DoneAt != "" { + fmt.Println("request.EndAt", request.DoneAt) + done_at, err := time.Parse("2006-01-02T15:04:05-07:00", request.DoneAt) + if err == nil { + userQuizPaper.DoneAt = &done_at + } + fmt.Println("done_at", done_at) + } + + fmt.Println("userQuizPaper", userQuizPaper) + result, err := controller.service.Update(userQuizPaper) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + fmt.Println("result", result) + c.JSON(http.StatusOK, result) +} diff --git a/internal/database/database.go b/internal/database/database.go new file mode 100644 index 0000000..f99d3ec --- /dev/null +++ b/internal/database/database.go @@ -0,0 +1,44 @@ +package database + +import ( + "fmt" + config "learnsteam/cslms-api/configs" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/schema" +) + +type Database struct { + *gorm.DB +} + +var DB *gorm.DB + +func Init() { + DB = ConnectDB((config.DATABASE_URL)) +} + +func ConnectDB(url string) *gorm.DB { + gorm_config := gorm.Config{NamingStrategy: schema.NamingStrategy{SingularTable: true}} + db, err := gorm.Open(mysql.Open(url), &gorm_config) + if err != nil { + panic(err) + } + return db +} + +func GetDB() *gorm.DB { + return DB +} + +func AutoMigrate() { + fmt.Println("AugoMigrate") + // DB.AutoMigrate( + // &models.User{}, + // &models.Token{}, + // &models.Program{}, + // &models.Quiz{}, + // &models.Exam{}, + // ) +} diff --git a/internal/helpers/cipher/cipher.go b/internal/helpers/cipher/cipher.go new file mode 100644 index 0000000..7aae840 --- /dev/null +++ b/internal/helpers/cipher/cipher.go @@ -0,0 +1,86 @@ +package cipher + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "encoding/base64" + "strings" +) + +type Crypto interface { + Encrypt(plainText string) (string, error) + Decrypt(cipherIvKey string) (string, error) +} + +type niceCrypto struct { + cipherKey string + cipherIvKey string +} + +func (c niceCrypto) Encrypt(plainText string) (string, error) { + if strings.TrimSpace(plainText) == "" { + return plainText, nil + } + + block, err := aes.NewCipher([]byte(c.cipherKey)) + if err != nil { + return "", err + } + + encrypter := cipher.NewCBCEncrypter(block, []byte(c.cipherIvKey)) + paddedPlainText := padPKCS7([]byte(plainText), encrypter.BlockSize()) + + cipherText := make([]byte, len(paddedPlainText)) + // CryptBlocks 함수에 데이터(paddedPlainText)와 암호화 될 데이터를 저장할 슬라이스(cipherText)를 넣으면 암호화가 된다. + encrypter.CryptBlocks(cipherText, paddedPlainText) + + return base64.StdEncoding.EncodeToString(cipherText), nil +} + +func (c niceCrypto) Decrypt(cipherText string) (string, error) { + if strings.TrimSpace(cipherText) == "" { + return cipherText, nil + } + + decodedCipherText, err := base64.StdEncoding.DecodeString(cipherText) + if err != nil { + return "", err + } + + block, err := aes.NewCipher([]byte(c.cipherKey)) + if err != nil { + return "", err + } + + decrypter := cipher.NewCBCDecrypter(block, []byte(c.cipherIvKey)) + plainText := make([]byte, len(decodedCipherText)) + + decrypter.CryptBlocks(plainText, decodedCipherText) + trimmedPlainText := trimPKCS5(plainText) + + return string(trimmedPlainText), nil +} + +func NewNiceCrypto(cipherKey, cipherIvKey string) (Crypto, error) { + if ck := len(cipherKey); ck != 32 { + return nil, aes.KeySizeError(ck) + } + + if cik := len(cipherIvKey); cik != 16 { + return nil, aes.KeySizeError(cik) + } + + return &niceCrypto{cipherKey, cipherIvKey}, nil +} + +func padPKCS7(plainText []byte, blockSize int) []byte { + padding := blockSize - len(plainText)%blockSize + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(plainText, padText...) +} + +func trimPKCS5(text []byte) []byte { + padding := text[len(text)-1] + return text[:len(text)-int(padding)] +} diff --git a/internal/helpers/helpers.go b/internal/helpers/helpers.go new file mode 100644 index 0000000..8a7fea1 --- /dev/null +++ b/internal/helpers/helpers.go @@ -0,0 +1,19 @@ +package helpers + +import ( + "os" + "time" +) + +func InLambda() bool { + if lambdaTaskRoot := os.Getenv("LAMBDA_TASK_ROOT"); lambdaTaskRoot != "" { + return true + } + return false +} + +func Datetime(timeString string) (time.Time, error) { + layout := "2006-01-02T15:04:05.000Z" + result, err := time.Parse(layout, timeString) + return result, err +} diff --git a/internal/helpers/securityutil/securityutil.go b/internal/helpers/securityutil/securityutil.go new file mode 100644 index 0000000..cc1b8fa --- /dev/null +++ b/internal/helpers/securityutil/securityutil.go @@ -0,0 +1,150 @@ +package securityutil + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "fmt" + + "golang.org/x/crypto/bcrypt" +) + +func SHA256(data string) string { + hash := sha256.New() + hash.Write([]byte(data)) + return hex.EncodeToString(hash.Sum(nil)) +} + +func HashPassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) + return string(bytes), err +} + +func CheckPasswordHash(password string, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} + +func AES256Encode(plaintext string, key string, iv string, blockSize int) string { + bKey := []byte(key) + bIV := []byte(iv) + bPlaintext := PKCS5Padding([]byte(plaintext), blockSize, len(plaintext)) + block, err := aes.NewCipher(bKey) + if err != nil { + panic(err) + } + ciphertext := make([]byte, len(bPlaintext)) + mode := cipher.NewCBCEncrypter(block, bIV) + mode.CryptBlocks(ciphertext, bPlaintext) + // return hex.EncodeToString(ciphertext) + fmt.Printf("hex.EncodeToString(ciphertext) : %s\n", hex.EncodeToString(ciphertext)) + fmt.Printf("base64.StdEncoding.EncodeToString(ciphertext) : %s\n", base64.StdEncoding.EncodeToString(ciphertext)) + fmt.Printf("base64.RawStdEncoding.EncodeToString(ciphertext) : %s\n", base64.RawStdEncoding.EncodeToString(ciphertext)) + return base64.StdEncoding.EncodeToString(ciphertext) +} + +func AES256Decode(cipherText string, encKey string, iv string) (decryptedString string) { + bKey := []byte(encKey) + bIV := []byte(iv) + // cipherTextDecoded, err := hex.DecodeString(cipherText) + cipherTextDecoded, err := base64.StdEncoding.DecodeString(cipherText) + if err != nil { + panic(err) + } + + block, err := aes.NewCipher(bKey) + if err != nil { + panic(err) + } + + mode := cipher.NewCBCDecrypter(block, bIV) + mode.CryptBlocks([]byte(cipherTextDecoded), []byte(cipherTextDecoded)) + return string(cipherTextDecoded) +} + +func PKCS5Padding(ciphertext []byte, blockSize int, after int) []byte { + padding := (blockSize - len(ciphertext)%blockSize) + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} + +// func pkcs7Pad(b []byte, blocksize int) ([]byte, error) { +// if blocksize <= 0 { +// return nil, ErrInvalidBlockSize +// } +// if b == nil || len(b) == 0 { +// return nil, ErrInvalidPKCS7Data +// } +// n := blocksize - (len(b) % blocksize) +// pb := make([]byte, len(b)+n) +// copy(pb, b) +// copy(pb[len(b):], bytes.Repeat([]byte{byte(n)}, n)) +// return pb, nil +// } + +// // pkcs7Unpad validates and unpads data from the given bytes slice. +// // The returned value will be 1 to n bytes smaller depending on the +// // amount of padding, where n is the block size. +// func pkcs7Unpad(b []byte, blocksize int) ([]byte, error) { +// if blocksize <= 0 { +// return nil, ErrInvalidBlockSize +// } +// if b == nil || len(b) == 0 { +// return nil, ErrInvalidPKCS7Data +// } +// if len(b)%blocksize != 0 { +// return nil, ErrInvalidPKCS7Padding +// } +// c := b[len(b)-1] +// n := int(c) +// if n == 0 || n > len(b) { +// return nil, ErrInvalidPKCS7Padding +// } +// for i := 0; i < n; i++ { +// if b[len(b)-n+i] != c { +// return nil, ErrInvalidPKCS7Padding +// } +// } +// return b[:len(b)-n], nil +// } + +// // func Encrypt(b cipher.Block, plaintext []byte) []byte { +// // if mod := len(plaintext) % aes.BlockSize; mod != 0 { // 블록 크기의 배수가 되어야함 +// // padding := make([]byte, aes.BlockSize-mod) // 블록 크기에서 모자라는 부분을 +// // plaintext = append(plaintext, padding...) // 채워줌 +// // } + +// // ciphertext := make([]byte, aes.BlockSize+len(plaintext)) // 초기화 벡터 공간(aes.BlockSize)만큼 더 생성 +// // // iv := ciphertext[:aes.BlockSize] // 부분 슬라이스로 초기화 벡터 공간을 가져옴 +// // // if _, err := io.ReadFull(rand.Reader, iv); err != nil { // 랜덤 값을 초기화 벡터에 넣어줌 +// // // fmt.Println(err) +// // // return nil +// // // } +// // iv := []byte("0000000000000000") +// // mode := cipher.NewCBCEncrypter(b, iv) // 암호화 블록과 초기화 벡터를 넣어서 암호화 블록 모드 인스턴스 생성 +// // mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext) // 암호화 블록 모드 인스턴스로 +// // // 암호화 + +// // return ciphertext +// // } + +// // func Decrypt(b cipher.Block, ciphertext []byte) []byte { +// // if len(ciphertext)%aes.BlockSize != 0 { // 블록 크기의 배수가 아니면 리턴 +// // fmt.Println("암호화된 데이터의 길이는 블록 크기의 배수가 되어야합니다.") +// // return nil +// // } + +// // // iv := ciphertext[:aes.BlockSize] // 부분 슬라이스로 초기화 벡터 공간을 가져옴 +// // iv := []byte("0000000000000000") +// // ciphertext = ciphertext[aes.BlockSize:] // 부분 슬라이스로 암호화된 데이터를 가져옴 + +// // plaintext := make([]byte, len(ciphertext)) // 평문 데이터를 저장할 공간 생성 +// // mode := cipher.NewCBCDecrypter(b, iv) // 암호화 블록과 초기화 벡터를 넣어서 +// // // 복호화 블록 모드 인스턴스 생성 +// // mode.CryptBlocks(plaintext, ciphertext) // 복호화 블록 모드 인스턴스로 복호화 + +// // return plaintext +// // } diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go new file mode 100644 index 0000000..b98f875 --- /dev/null +++ b/internal/middleware/auth.go @@ -0,0 +1,136 @@ +package middleware + +import ( + "errors" + "fmt" + "net/http" + "strings" + + config "learnsteam/cslms-api/configs" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" +) + +func Auth(permission string) gin.HandlerFunc { + return func(c *gin.Context) { + sub, err := UserID(c.Request) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + c.Abort() + return + } + + role, err := Role(c.Request) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + c.Abort() + return + } + + if permission != "member" && *role != permission { + fmt.Println("permission", permission, "role", *role) + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + c.Abort() + return + } + + fmt.Println("token", extract(c.Request)) + fmt.Println("sub", sub) + + c.Set("token", extract(c.Request)) + c.Set("sub", sub) + c.Set("role", role) + c.Next() + } +} + +func Permission(permission *string) gin.HandlerFunc { + return func(c *gin.Context) { + sub, err := UserID(c.Request) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + c.Abort() + return + } + + role, err := Role(c.Request) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + c.Abort() + return + } + + if role != permission { + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + c.Abort() + return + } + + fmt.Println("token", extract(c.Request)) + fmt.Println("sub", sub) + + c.Set("token", extract(c.Request)) + c.Set("sub", sub) + c.Set("role", role) + c.Next() + } +} + +func extract(r *http.Request) string { + authorization := r.Header.Get("Authorization") + strArr := strings.Split(authorization, " ") + if len(strArr) == 2 { + return strArr[1] + } + return "" +} + +func verify(r *http.Request) (*jwt.Token, error) { + tokenString := extract(r) + jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) { + if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", jwtToken.Header["alg"]) + } + return []byte(config.SECRET_KEY), nil + }) + + return jwtToken, err +} + +func UserID(r *http.Request) (int64, error) { + jwtToken, err := verify(r) + if err != nil { + return -1, err + } + + claims, ok := jwtToken.Claims.(jwt.MapClaims) + if !ok || !jwtToken.Valid { + return -1, errors.New("refresh token is invalid") + } + + sub := claims["sub"].(float64) + user_id := int64(sub) + if err != nil { + return -1, err + } + fmt.Println(user_id) + + return user_id, nil +} + +func Role(r *http.Request) (*string, error) { + jwtToken, err := verify(r) + if err != nil { + return nil, err + } + + claims, ok := jwtToken.Claims.(jwt.MapClaims) + if !ok || !jwtToken.Valid { + return nil, errors.New("refresh token is invalid") + } + + role := claims["role"].(string) + + return &role, nil +} diff --git a/internal/middleware/transaction.go b/internal/middleware/transaction.go new file mode 100644 index 0000000..87807ac --- /dev/null +++ b/internal/middleware/transaction.go @@ -0,0 +1,44 @@ +package middleware + +import ( + "log" + "net/http" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func StatusInList(status int, statusList []int) bool { + for _, i := range statusList { + for i == status { + return true + } + } + return false +} + +func Transaction(db *gorm.DB) gin.HandlerFunc { + return func(c *gin.Context) { + txHandle := db.Begin() + log.Print("begining database transaction") + + defer func() { + if r := recover(); r != nil { + txHandle.Rollback() + } + }() + + c.Set("db_trx", txHandle) + c.Next() + + if StatusInList(c.Writer.Status(), []int{http.StatusOK, http.StatusCreated}) { + log.Print("committing transactions") + if err := txHandle.Commit().Error; err != nil { + log.Print("transaction commit error: ", err) + } else { + log.Print("rollback transaction due to status code: ", c.Writer.Status()) + txHandle.Rollback() + } + } + } +} diff --git a/internal/models/auth.go b/internal/models/auth.go new file mode 100644 index 0000000..6dc821a --- /dev/null +++ b/internal/models/auth.go @@ -0,0 +1,29 @@ +package models + +type LoginRequest struct { + Username string `json:"user_name" example:"admin0"` + Password string `json:"password" example:"testme"` +} + +type LoginResponse struct { + User User `json:"user"` + Token string `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDI3MTMwMjcsInN1"` + RefreshToken string `json:"refresh_token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDc4OTcwMjcs"` +} + +type RegisterRequest struct { + FirstName string `json:"first_name" example:"길순"` + LastName string `json:"last_name" example:"홍"` + Username string `json:"user_name" example:"gilsoon"` + Password string `json:"password" example:"StrongPass!@#$"` + Gender string `json:"gender" example:"F"` + UserRole string `json:"user_role" example:"member"` + Phone string `json:"phone_cs" example:"01012345678"` + Image string `json:"upload_image" example:""` +} + +type RegisterResponse struct { + User User `json:"user"` + Token string `json:"token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDI3MTMwMjcsInN1"` + RefreshToken string `json:"refresh_token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MDc4OTcwMjcs"` +} diff --git a/internal/models/center.go b/internal/models/center.go new file mode 100644 index 0000000..cb3f57a --- /dev/null +++ b/internal/models/center.go @@ -0,0 +1,57 @@ +package models + +import ( + "time" +) + +// CREATE TABLE `Centers` ( +// `id` bigint(20) NOT NULL AUTO_INCREMENT, +// `guid_id` varchar(36) DEFAULT '', +// `center_title` varchar(50) DEFAULT '' COMMENT '강동런스팀로봇코딩학원', +// `center_name` varchar(50) DEFAULT '' COMMENT 'learnsteam_kd', +// `owner_id` bigint(20) DEFAULT 0, +// `content_page` text DEFAULT '' COMMENT '학원상세페이지, html/마크다운/text', +// `company_info` text DEFAULT '' COMMENT '사업자정보-json\r\n기타 정보 추가 가능\r\n\r\n{\r\n "company_ceo": "대표자이름",\r\n "company_code": "사업자번호",\r\n "company_phone": "대표전화번호",\r\n "company_email": "대표전자메일",\r\n "company_homepage": "대표홈페이지 url",\r\n "company_logo": "로고이미지 url",\r\n "company_cover_image": "대표 이미지", \r\n}', +// `memo` varchar(256) DEFAULT '', +// `created_at` timestamp NULL DEFAULT current_timestamp(), +// `updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), +// `status` varchar(10) DEFAULT NULL, +// PRIMARY KEY (`id`) +// ) ENGINE=InnoDB AUTO_INCREMENT=1000005 DEFAULT CHARSET=utf8mb4 COMMENT='Centers의 이미지/영상은 Links에서 관리됨. '; + +type Center struct { + ID int64 `json:"id" db:"id" example:"100001" gorm:"column:id;primary_key;"` + GUID string `json:"guid_id" db:"guid_id" example:"2036023a-fb56-4b6c-b3bb-c787c681ada6" gorm:"column:guid_id;size:255;index;"` + Title string `json:"center_title" db:"center_title" example:"강동런스팀로봇코딩학원" gorm:"column:center_title;size:50;index;"` + Name string `json:"center_name" db:"center_name" example:"learnsteam_kd" gorm:"column:center_name;size:50;index;"` + OwnerID int64 `json:"owner_id" db:"owner_id" example:"100001" gorm:"column:owner_id;index;"` + Content string `json:"content_page" db:"content_page" example:"학원상세페이지, html/마크다운/text" gorm:"column:content_page;size:2048;"` + Info string `json:"company_info" db:"company_info" example:"사업자정보-json 기타 정보 추가 가능" gorm:"column:company_info;size:2048;"` + Memo string `json:"memo" db:"memo" example:"메모" gorm:"column:memo;size:1024;"` + Status string `json:"status" db:"status" example:"on" gorm:"column:status;size:10;index;"` + UpdatedAt time.Time `json:"-" gorm:"column:updated_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index;->:false"` + CreatedAt time.Time `json:"-" gorm:"column:created_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;index;->:false;<-:create"` +} + +// 테이블이름 정의 +func (Center) TableName() string { + return "Centers" +} + +type CenterRequest struct { + Title string `json:"center_title" example:"강동런스팀로봇코딩학원"` + Name string `json:"center_name" example:"learnsteam_kd"` + OwnerID int64 `json:"owner_id" example:"100001"` + Content string `json:"content_page" example:"학원상세페이지, html/마크다운/text"` + Info string `json:"company_info" example:"사업자정보-json 기타 정보 추가 가능"` + Memo string `json:"memo" example:"메모"` + Status string `json:"status" example:"on"` +} + +type CenterListResponse struct { + Data []Center `json:"data"` + Total int64 `json:"total" example:"5"` + Page int `json:"page" example:"1"` + TotalPage int64 `json:"totalPage" example:"1"` + PageSize int `json:"pageSize" example:"10"` +} diff --git a/internal/models/quiz.go b/internal/models/quiz.go new file mode 100644 index 0000000..d11f2d7 --- /dev/null +++ b/internal/models/quiz.go @@ -0,0 +1,78 @@ +package models + +import ( + "time" + + "gorm.io/datatypes" +) + +// 문항 json, +// option1~4는 구분이름일 뿐, 실제로는 셔플되어서 반환됨. +/* +{ + "option1": {"content": "markdown 문서로 작성됨", "is_answer": false}, + "option2": {"content": "markdown 문서로 작성됨", "is_answer": false}, + "option3": {"content": "markdown 문서로 작성됨", "is_answer": false}, + "option4": {"content": "markdown 문서로 작성됨", "is_answer": true}, + "hint": "markdown문서로 작성됨", + "score": 10 +} +*/ + +type Quiz struct { + ID int64 `json:"id" db:"id" example:"1000001" gorm:"column:id;primary_key;"` + GUID string `json:"guid_id" db:"guid_id" example:"2036023a-fb56-4b6c-b3bb-c787c681ada6" gorm:"column:guid_id;size:255;index;"` + CenterID int64 `json:"center_id" db:"center_id" example:"100001" gorm:"column:center_id;index;"` + QuizPaperID int64 `json:"quiz_paper_id" db:"quiz_paper_id" example:"100001" gorm:"column:quiz_paper_id;index;"` + No int `json:"vol_no" db:"vol_no" example:"5" gorm:"column:vol_no;index;"` + QuestionType string `json:"question_type" db:"question_type" example:"choice" gorm:"column:question_type;size:10;index;"` + Question string `json:"question" db:"question" example:"퀴즈 질문입니다." gorm:"column:question;size:512;"` + Content datatypes.JSON `json:"content" db:"content" gorm:"column:content;"` + Answer datatypes.JSON `json:"answer" db:"answer" gorm:"column:answer;"` + UpdatedAt time.Time `json:"-" gorm:"column:updated_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index;->:false"` + CreatedAt time.Time `json:"-" gorm:"column:created_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;index;->:false;<-:create"` +} + +// 테이블이름 정의 +func (Quiz) TableName() string { + return "Quizs" +} + +type QuizRequest struct { + QuizPaperID int64 `json:"quiz_paper_id" example:"1000001"` + CenterID int64 `json:"center_id" example:"1000001"` + No int `json:"vol_no" example:"1"` + QuestionType string `json:"question_type" example:"choice"` + Question string `json:"question" example:"질문입니다."` + Content QuizContent `json:"content"` + Answer []string `json:"answer" example:"option1,option2"` +} + +type QuizResponse struct { + ID int64 `json:"id" example:"1000001"` + QuizPaperID int64 `json:"quiz_paper_id" example:"1000001"` + No int `json:"vol_no" example:"5"` + QuestionType string `json:"question_type" example:"check"` + Question string `json:"question" example:"퀴즈 질문입니다."` + Content QuizContent `json:"content"` + Answer []string `json:"answer" example:"option1,option2"` +} + +type QuizListResponse struct { + Data []Quiz `json:"data"` + Total int64 `json:"total" example:"5"` + Page int `json:"page" example:"1"` + TotalPage int64 `json:"totalPage" example:"1"` + PageSize int `json:"pageSize" example:"10"` +} + +type QuizContent struct { + Option1 string `json:"option1" example:"markdown 문서로 작성됨"` + Option2 string `json:"option2" example:"markdown 문서로 작성됨"` + Option3 string `json:"option3" example:"markdown 문서로 작성됨"` + Option4 string `json:"option4" example:"markdown 문서로 작성됨"` + Hint string `json:"hint" example:"markdown문서로 작성됨"` +} + +type QuizAnswer struct { +} diff --git a/internal/models/quizpaper.go b/internal/models/quizpaper.go new file mode 100644 index 0000000..7b917c5 --- /dev/null +++ b/internal/models/quizpaper.go @@ -0,0 +1,82 @@ +package models + +import ( + "time" + + "gorm.io/datatypes" +) + +// CREATE TABLE `QuizPapers` ( +// `id` bigint(20) NOT NULL AUTO_INCREMENT, +// `guid_id` char(36) NOT NULL DEFAULT '0', +// `center_id` bigint(20) NOT NULL DEFAULT 0, +// `manager_id` bigint(20) NOT NULL DEFAULT 0, +// `title` varchar(256) NOT NULL DEFAULT '' COMMENT '퀴즈시트 제목', +// `status` varchar(10) NOT NULL DEFAULT '0' COMMENT 'on/off', +// `content` text NOT NULL DEFAULT '' COMMENT 'markdown 문서', +// `tags` varchar(256) NOT NULL DEFAULT '' COMMENT '출력,hello world,for반복문...', +// `category` varchar(256) NOT NULL DEFAULT '파이썬기본', +// `created_at` timestamp NOT NULL DEFAULT current_timestamp(), +// `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), +// PRIMARY KEY (`id`) USING BTREE, +// KEY `guid_id` (`guid_id`) USING BTREE, +// KEY `center_id` (`center_id`) USING BTREE, +// KEY `manager_id` (`manager_id`) USING BTREE +// ) ENGINE=InnoDB AUTO_INCREMENT=10000001 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='퀴즈시험지 관리 테이블\r\n\r\n'; + +type QuizPaper struct { + ID int64 `json:"id" db:"id" example:"100001" gorm:"column:id;primary_key;"` + GUID string `json:"guid_id" db:"guid_id" example:"ef74c59a-c707-4162-a52b-455906c81ec1" gorm:"column:guid_id;size:255;index;"` + CenterID int64 `json:"center_id" db:"center_id" example:"100001" gorm:"column:center_id;index;"` + ManagerID int64 `json:"manager_id" db:"manager_id" example:"100001" gorm:"column:manager_id;index;"` + Title string `json:"title" db:"title" example:"퀴즈 시트 제목" gorm:"column:title;size:255;index;"` + Content string `json:"content" db:"content" example:"퀴즈 시트 설명" gorm:"column:content;size:512;"` + Category string `json:"category" db:"category" example:"파이썬기본" gorm:"column:title;size:255;"` + Tag datatypes.JSON `json:"tag" db:"tag" gorm:"column:tag;"` + Status string `json:"status" example:"on" gorm:"column:status;size:10;index;"` + UpdatedAt time.Time `json:"-" gorm:"column:updated_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index;->:false"` + CreatedAt time.Time `json:"-" gorm:"column:created_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;index;->:false;<-:create"` +} + +// 테이블이름 정의 +func (QuizPaper) TableName() string { + return "QuizPapers" +} + +type QuizPaperRequest struct { + CenterID int64 `json:"center_id" example:"100001"` + Title string `json:"title" example:"퀴즈 시트 제목"` + Content string `json:"content" example:"퀴즈 시트 설명"` + Category string `json:"category" example:"파이썬기본"` + Tag datatypes.JSON `json:"tag"` + Status string `json:"status" example:"on"` +} + +type QuizPaperResponse struct { + ID int64 `json:"id" example:"100001"` + CenterID int64 `json:"center_id" example:"100001"` + ManagerID int64 `json:"manager_id" example:"100001"` + Title string `json:"title" example:"퀴즈 시트 제목"` + Content string `json:"content" example:"퀴즈 시트 설명"` + Category string `json:"category" example:"파이썬기본"` + Tag datatypes.JSON `json:"tag"` + Status string `json:"status" example:"on"` +} + +type QuizPaperListResponse struct { + Data []QuizPaper `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"` +} + +type QuizPaperCopyRequest struct { + Title string `json:"title" example:"퀴즈 시트 제목"` + CenterID int64 `json:"center_id" example:"100002"` +} + +type QuizPaperCopyResponse struct { + QuizPaper QuizPaper `json:"quiz_paper"` + Quiz []Quiz `json:"quiz"` +} diff --git a/internal/models/token.go b/internal/models/token.go new file mode 100644 index 0000000..87fde2d --- /dev/null +++ b/internal/models/token.go @@ -0,0 +1,46 @@ +package models + +import "time" + +// CREATE TABLE `UserTokens` ( +// `id` bigint(20) NOT NULL AUTO_INCREMENT, +// `user_id` bigint(20) DEFAULT 0, +// `token` varchar(256) DEFAULT '', +// `refresh_token` varchar(256) DEFAULT '', +// `status` varchar(3) DEFAULT 'on' COMMENT 'on/off', +// `register_at` timestamp NULL DEFAULT current_timestamp(), +// `ending_at` timestamp NULL DEFAULT NULL COMMENT '종료날짜', +// PRIMARY KEY (`id`) USING BTREE +// ) ENGINE=InnoDB AUTO_INCREMENT=1000017 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC; + +type Token struct { + ID int64 `json:"-" db:"id" gorm:"primary_key"` + UserID int64 `json:"user_id" db:"user_id" gorm:"index;"` + Token string `json:"token" db:"token" gorm:"size:255;index;"` + RefreshToken string `json:"refresh_token" db:"refresh_token" gorm:"size:255;index;"` + + Status string `json:"status" gorm:"size:3;index"` + + RegisterAt time.Time `json:"register_at" db:"register_at"` + EndingAt time.Time `json:"ending_at" db:"ending_at"` +} + +// 테이블이름 정의 +func (Token) TableName() string { + return "UserTokens" +} + +type RefreshTokenRequest struct { + RefreshToken string `json:"refresh_token" binding:"required"` +} + +type TokenResponse struct { + Token string `json:"token"` + TokenBody TokenBody `json:"tokenBody"` +} + +type TokenBody struct { + ExpireAt time.Time `json:"tokenExpiredDate"` + TokenIdx int `json:"tokenIdx"` + TokenType int `json:"tokenType"` +} diff --git a/internal/models/user.go b/internal/models/user.go new file mode 100644 index 0000000..e4419ca --- /dev/null +++ b/internal/models/user.go @@ -0,0 +1,52 @@ +package models + +import "time" + +// CREATE TABLE `Users` ( +// `id` bigint(20) NOT NULL AUTO_INCREMENT, +// `guid_id` longtext DEFAULT NULL, +// `first_name` longtext DEFAULT NULL, +// `last_name` longtext DEFAULT NULL, +// `user_name` longtext DEFAULT NULL, +// `password` longtext DEFAULT NULL, +// `gender` char(1) DEFAULT 'M' COMMENT 'M(남)/F(여)', +// `user_role` longtext DEFAULT NULL, +// `memo_cs` longtext DEFAULT NULL, +// `register_at` datetime(3) DEFAULT NULL, +// `updated_at` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '업데이트될때마다 자동저장', +// `phone_cs` longtext DEFAULT NULL, +// `upload_image` longtext DEFAULT NULL, +// PRIMARY KEY (`id`) USING BTREE, +// KEY `guid_id` (`guid_id`(768)), +// KEY `first_name` (`first_name`(768)), +// KEY `last_name` (`last_name`(768)), +// KEY `user_name` (`user_name`(768)) +// ) ENGINE=InnoDB AUTO_INCREMENT=1000023 DEFAULT CHARSET=utf8mb4; + +type User struct { + ID int64 `json:"id" db:"id" example:"100001" gorm:"column:id;primary_key;"` + GUID string `json:"guid_id" db:"guid_id" example:"137c1683-2ad6-4201-b256-253828b61c49" gorm:"column:guid_id;size:255;"` + FirstName string `json:"first_name" db:"first_name" example:"길순" gorm:"column:first_name;size:255;"` + LastName string `json:"last_name" db:"last_name" example:"홍" gorm:"column:last_name;size:255;"` + Username string `json:"user_name" db:"user_name" example:"user0" gorm:"column:user_name;size:50;uniqueIndex;"` + Password string `json:"-" db:"password" gorm:"column:password;size:255;not null;"` + Gender string `json:"gender" db:"gender" example:"F" gorm:"column:gender;size:1;"` + UserRole string `json:"user_role" db:"user_role" example:"member" gorm:"column:user_role;size:255;"` + Memo string `json:"memo_cs" db:"memo_cs" example:"사용자 메모" gorm:"column:memo_cs;size:255;"` + Phone string `json:"phone_cs" db:"phone_cs" example:"010-1234-5678" gorm:"column:phone_cs;size:255;"` + Image string `json:"upload_image" db:"upload_image" example:"image_url" gorm:"column:upload_image;size:255;"` + RegisterAt time.Time `json:"register_at" db:"regiser_at" gorm:"column:register_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;index;->:false"` +} + +// 테이블이름 정의 +func (User) TableName() string { + return "Users" +} + +type UserListResponse struct { + Data []User `json:"data"` + Total int64 `json:"total" example:"90"` + Page int `json:"page" example:"1"` + TotalPage int64 `json:"totalPage" example:"9"` + PageSize int `json:"pageSize" example:"10"` +} diff --git a/internal/models/userquiz.go b/internal/models/userquiz.go new file mode 100644 index 0000000..7651fab --- /dev/null +++ b/internal/models/userquiz.go @@ -0,0 +1,68 @@ +package models + +import ( + "time" + + "gorm.io/datatypes" +) + +type UserQuiz struct { + ID int64 `json:"id" db:"id" example:"1000001" gorm:"column:id;primary_key;"` + GUID string `json:"guid_id" db:"guid_id" example:"2036023a-fb56-4b6c-b3bb-c787c681ada6" gorm:"column:guid_id;size:255;index;"` + CenterID int64 `json:"center_id" db:"center_id" example:"100001" gorm:"column:center_id;index;"` + UserQuizPaperID int64 `json:"user_quiz_paper_id" db:"user_quiz_paper_id" example:"1000001" gorm:"column:user_quiz_paper_id;index;"` + UserID int64 `json:"user_id" db:"user_id" example:"1000001" gorm:"column:user_id;index;"` + No int `json:"vol_no" db:"vol_no" example:"5" gorm:"column:vol_no;index;"` + QuestionType string `json:"question_type" db:"question_type" example:"choice" gorm:"column:question_type;size:10;index;"` + Question string `json:"question" db:"question" example:"퀴즈 질문입니다." gorm:"column:question;size:512;"` + Content datatypes.JSON `json:"content" db:"content" gorm:"column:content;"` + Answer datatypes.JSON `json:"answer,omitempty" db:"answer" gorm:"column:answer;default:'[]';"` + Status string `json:"status" db:"status" example:"waiting" gorm:"column:status;size:10;index;"` + Result string `json:"result" db:"result" example:"success" gorm:"column:result;size:10;index;"` + Score float32 `json:"score" db:"score" example:"10" gorm:"column:score;"` + UpdatedAt time.Time `json:"-" gorm:"column:updated_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index;->:false"` + CreatedAt time.Time `json:"-" gorm:"column:created_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;index;->:false;<-:create"` +} + +// 테이블이름 정의 +func (UserQuiz) TableName() string { + return "UserQuizs" +} + +type UserQuizRequest struct { + CenterID int64 `json:"center_id" example:"1000001"` + UserQuizPaperID int64 `json:"quiz_paper_id" example:"1000001"` + UserID int64 `json:"user_id" example:"1000001"` + No int `json:"vol_no" example:"1"` + QuestionType string `json:"question_type" example:"choice"` + Question string `json:"question" example:"질문입니다."` + Content QuizContent `json:"content"` + Answer []string `json:"answer" example:"option1,option2"` + Status string `json:"status" example:"waiting"` + Result string `json:"result" example:"success"` + Score float32 `json:"score" example:"10"` +} + +type UserQuizResponse struct { + ID int64 `json:"id" example:"1000001"` + GUID string `json:"guid_id" example:"2036023a-fb56-4b6c-b3bb-c787c681ada6"` + CenterD string `json:"center_id" example:"2036023a-fb56-4b6c-b3bb-c787c681ada6"` + UserQuizPaperID int64 `json:"user_quiz_paper_id" example:"1000001"` + UserID int64 `json:"user_id" example:"1000001"` + No int `json:"vol_no" example:"5"` + QuestionType string `json:"question_type" example:"check"` + Question string `json:"question" example:"퀴즈 질문입니다."` + Content QuizContent `json:"content"` + Answer []string `json:"answer" example:"option1,option2"` + Status string `json:"status" example:"waiting"` + Result string `json:"result" example:"success"` + Score float32 `json:"score" example:"10"` +} + +type UserQuizListResponse struct { + Data []UserQuizResponse `json:"data"` + Total int64 `json:"total" example:"5"` + Page int `json:"page" example:"1"` + TotalPage int64 `json:"totalPage" example:"1"` + PageSize int `json:"pageSize" example:"10"` +} diff --git a/internal/models/userquizpaper.go b/internal/models/userquizpaper.go new file mode 100644 index 0000000..22c3834 --- /dev/null +++ b/internal/models/userquizpaper.go @@ -0,0 +1,91 @@ +package models + +import ( + "time" + + "gorm.io/datatypes" +) + +// CREATE TABLE `UserQuizPapers` ( +// `id` bigint(20) NOT NULL AUTO_INCREMENT, +// `guid_id` char(36) NOT NULL DEFAULT '0', +// `center_id` bigint(20) NOT NULL DEFAULT 0, +// `quiz_paper_id` bigint(20) NOT NULL DEFAULT 0, +// `status` varchar(10) NOT NULL DEFAULT '0' COMMENT '매칭된 학생퀴즈시험지 상태: \r\nwaiting(대기)/processing(시험진행중)/abort(취소)/marking(채점중)/done(완료) ', +// `user_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '매칭된 학생', +// `user_score` float NOT NULL DEFAULT 5 COMMENT '10', +// `total_score` float NOT NULL DEFAULT 100 COMMENT '100.0, 5.0', +// `start_at` timestamp NULL DEFAULT NULL COMMENT '학생시험시작시각', +// `done_at` timestamp NULL DEFAULT NULL COMMENT '학생시험종료시각', +// `created_at` timestamp NOT NULL DEFAULT current_timestamp(), +// `updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), +// PRIMARY KEY (`id`) USING BTREE, +// KEY `guid_id` (`guid_id`) USING BTREE, +// KEY `center_id` (`center_id`) USING BTREE, +// KEY `user_id` (`user_id`), +// KEY `manager_id` (`quiz_paper_id`) USING BTREE +// ) ENGINE=InnoDB AUTO_INCREMENT=10000001 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='학생퀴즈시험지 매칭\r\n'; + +type UserQuizPaper struct { + ID int64 `json:"id" db:"id" example:"1000015" gorm:"column:id;primary_key;"` + GUID string `json:"guid_id" db:"guid_id" example:"7f9329f5-2e36-4638-92d2-73064b7291a4" gorm:"column:guid_id;size:255;uniqueIndex"` + CenterID int64 `json:"center_id" db:"center_id" example:"1000015" gorm:"column:center_id;index;"` + QuizPaperID int64 `json:"quiz_paper_id" db:"quiz_paper_id" example:"1000001" gorm:"column:quiz_paper_id;index;"` + UserID int64 `json:"user_id" db:"user_id" example:"1000002" gorm:"column:user_id;index;"` + UserScore float32 `json:"user_score" db:"user_score" example:"5" gorm:"column:user_score;"` + TotalScore float32 `json:"total_score" db:"total_score" example:"100" gorm:"column:total_score;"` + Status string `json:"status" example:"wating" gorm:"column:status;size:10;index;"` + StartAt *time.Time `json:"start_at" gorm:"column:start_at;index;"` + DoneAt *time.Time `json:"done_at" gorm:"column:done_at;index;"` + UpdatedAt time.Time `json:"-" gorm:"column:updated_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;index;->:false"` + CreatedAt time.Time `json:"-" gorm:"column:created_at;type:DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;index;->:false;<-:create"` +} + +// 테이블이름 정의 +func (UserQuizPaper) TableName() string { + return "UserQuizPapers" +} + +type UserQuizPaperRequest struct { + QuizPaperID int64 `json:"quiz_paper_id" example:"1000002"` + Users datatypes.JSON `json:"users"` +} + +type UserQuizPaperResponse struct { + ID int64 `json:"id" db:"id" example:"1000015"` + GUID string `json:"guid_id" db:"guid_id" example:"7f9329f5-2e36-4638-92d2-73064b7291a4"` + CenterID int64 `json:"center_id" db:"center_id" example:"1000015"` + QuizPaperID int64 `json:"quiz_paper_id" db:"quiz_paper_id" example:"1000001"` + UserID int64 `json:"user_id" db:"user_id" example:"1000002"` + UserScore float32 `json:"user_score" db:"user_score" example:"5"` + TotalScore float32 `json:"total_score" db:"total_score" example:"100"` + Status string `json:"status" example:"wating"` + StartAt *time.Time `json:"start_at"` + DoneAt *time.Time `json:"done_at"` +} + +type UserQuizPaperUpdateRequest struct { + QuizPaperID int64 `json:"quiz_paper_id" example:"1000002"` + UserID int64 `json:"user_id" example:"1000003"` + UserScore float32 `json:"user_score" example:"5"` + TotalScore float32 `json:"total_score" example:"100"` + Status string `json:"status" example:"ready"` + StartAt string `json:"start_at,omitempty" example:"2023-11-10T13:10:00+09:00"` + DoneAt string `json:"done_at,omitempty" example:"2023-11-10T13:25:00+09:00"` +} + +type UserQuizPaperPatchRequest struct { + UserScore float32 `json:"user_score" example:"4"` + TotalScore float32 `json:"total_score" example:"80"` + Status string `json:"status" example:"ready"` + StartAt string `json:"start_at,omitempty" example:"2023-11-10T13:10:00+09:00"` + DoneAt string `json:"done_at,omitempty" example:"2023-11-10T13:25:00+09:00"` +} + +type UserQuizPaperListResponse struct { + Data []UserQuizPaper `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"` +} diff --git a/internal/repositories/center.go b/internal/repositories/center.go new file mode 100644 index 0000000..1479326 --- /dev/null +++ b/internal/repositories/center.go @@ -0,0 +1,89 @@ +package repositories + +import ( + "fmt" + "learnsteam/cslms-api/internal/models" + + "gorm.io/gorm" +) + +type centerRepository struct { + DB *gorm.DB +} + +func NewCenterRepository(db *gorm.DB) CenterRepository { + return ¢erRepository{ + DB: db, + } +} + +type CenterRepository interface { + List(string, int, int) (*[]models.Center, error) + Total(string) (int64, error) + Find(int64) (*models.Center, error) + FindByTitle(string) (*models.Center, error) + Create(*models.Center) (*models.Center, error) + Update(*models.Center) (*models.Center, error) + Delete(int64) error +} + +func (r *centerRepository) List(q string, page int, limit int) (*[]models.Center, error) { + var centers *[]models.Center + var err error + offset := limit * (page - 1) + fmt.Println("q", q) + if q != "" { + err = r.DB.Offset(offset).Limit(limit).Order("center_title ASC").Where("center_title LIKE ? OR center_name LIKE ?", "%"+q+"%", "%"+q+"%").Find(¢ers).Error + } else { + err = r.DB.Offset(offset).Limit(limit).Order("center_title ASC").Find(¢ers).Error + } + return centers, err +} + +func (r *centerRepository) Total(q string) (int64, error) { + var total int64 + var err error + if q != "" { + err = r.DB.Model(&models.Center{}).Where("center_title LIKE ? OR center_name LIKE ?", "%"+q+"%", "%"+q+"%").Count(&total).Error + } else { + err = r.DB.Model(&models.Center{}).Count(&total).Error + } + + return total, err +} + +func (r *centerRepository) Find(id int64) (*models.Center, error) { + var center *models.Center + err := r.DB.Where("id = ?", id).First(¢er).Error + return center, err +} + +func (r *centerRepository) FindByTitle(center_title string) (*models.Center, error) { + var center *models.Center + err := r.DB.Where("center_title = ?", center_title).First(¢er).Error + return center, err +} + +func (r *centerRepository) Create(center *models.Center) (*models.Center, error) { + err := r.DB.Create(¢er).Error + return center, err +} + +func (r *centerRepository) Update(center *models.Center) (*models.Center, error) { + var row *models.Center + if err := r.DB.Where("id=?", center.ID).First(&row).Error; err != nil { + return nil, err + } + + err := r.DB.Model(&row).Select("*").Updates(¢er).Error + return row, err +} + +func (r *centerRepository) Delete(id int64) error { + var center *models.Center + if err := r.DB.Where("id=?", id).First(¢er).Error; err != nil { + return err + } + err := r.DB.Delete(¢er).Error + return err +} diff --git a/internal/repositories/quiz.go b/internal/repositories/quiz.go new file mode 100644 index 0000000..eab7da8 --- /dev/null +++ b/internal/repositories/quiz.go @@ -0,0 +1,81 @@ +package repositories + +import ( + "learnsteam/cslms-api/internal/models" + + "gorm.io/gorm" +) + +type quizRepository struct { + DB *gorm.DB +} + +func NewQuizRepository(db *gorm.DB) QuizRepository { + return &quizRepository{ + DB: db, + } +} + +type QuizRepository interface { + List(int64, int, int) (*[]models.Quiz, error) + Total(int64) (int64, error) + + Find(int64) (*models.Quiz, error) + Create(*models.Quiz) (*models.Quiz, error) + Update(*models.Quiz) (*models.Quiz, error) + Delete(int64) error +} + +func (r *quizRepository) List(quiz_paper_id int64, page int, limit int) (*[]models.Quiz, error) { + var quizzes *[]models.Quiz + var err error + offset := limit * (page - 1) + if quiz_paper_id > 0 { + err = r.DB.Offset(offset).Limit(limit).Order("vol_no ASC").Where("quiz_paper_id = ?", quiz_paper_id).Find(&quizzes).Error + } else { + err = r.DB.Offset(offset).Limit(limit).Find(&quizzes).Error + } + return quizzes, err +} + +func (r *quizRepository) Total(quiz_paper_id int64) (int64, error) { + var total int64 + var err error + if quiz_paper_id > 0 { + err = r.DB.Model(&models.Quiz{}).Where("quiz_paper_id = ?", quiz_paper_id).Count(&total).Error + } else { + err = r.DB.Model(&models.Quiz{}).Count(&total).Error + } + + return total, err +} + +func (r *quizRepository) Find(id int64) (*models.Quiz, error) { + var quiz *models.Quiz + err := r.DB.Where("id = ?", id).First(&quiz).Error + return quiz, err +} + +func (r *quizRepository) Create(quiz *models.Quiz) (*models.Quiz, error) { + err := r.DB.Create(&quiz).Error + return quiz, err +} + +func (r *quizRepository) Update(quiz *models.Quiz) (*models.Quiz, error) { + var row *models.Quiz + if err := r.DB.Where("id=?", quiz.ID).First(&row).Error; err != nil { + return nil, err + } + + err := r.DB.Model(&row).Select("*").Updates(&quiz).Error + return row, err +} + +func (r *quizRepository) Delete(id int64) error { + var quiz *models.Quiz + if err := r.DB.Where("id=?", id).First(&quiz).Error; err != nil { + return err + } + err := r.DB.Delete(&quiz).Error + return err +} diff --git a/internal/repositories/quizpaper.go b/internal/repositories/quizpaper.go new file mode 100644 index 0000000..eb1da9e --- /dev/null +++ b/internal/repositories/quizpaper.go @@ -0,0 +1,90 @@ +package repositories + +import ( + "fmt" + "learnsteam/cslms-api/internal/models" + + "gorm.io/gorm" +) + +type quizPaperRepository struct { + DB *gorm.DB +} + +func NewQuizPaperRepository(db *gorm.DB) QuizPaperRepository { + return &quizPaperRepository{ + DB: db, + } +} + +type QuizPaperRepository interface { + List(string, string, int, int) (*[]models.QuizPaper, error) + Total(string, string) (int64, error) + + Find(int64) (*models.QuizPaper, error) + Create(*models.QuizPaper) (*models.QuizPaper, error) + Update(*models.QuizPaper) (*models.QuizPaper, error) + Delete(string) error +} + +func (r *quizPaperRepository) List(q string, tag string, page int, limit int) (*[]models.QuizPaper, error) { + var quizPapers *[]models.QuizPaper + var err error + offset := limit * (page - 1) + fmt.Println("q", q) + if q != "" { + fmt.Println(" 1 q", q) + err = r.DB.Offset(offset).Limit(limit).Order("created_at DESC").Where("title LIKE ? OR category LIKE ? OR tag LIKE ?", "%"+q+"%", "%"+q+"%", "%"+q+"%").Find(&quizPapers).Error + } else if tag != "" { + fmt.Println(" tag", tag) + err = r.DB.Offset(offset).Limit(limit).Order("created_at DESC").Where("tag LIKE ?", "%"+tag+"%").Find(&quizPapers).Error + } else { + fmt.Println(" query") + err = r.DB.Offset(offset).Limit(limit).Order("created_at DESC").Find(&quizPapers).Error + } + return quizPapers, err +} + +func (r *quizPaperRepository) Total(q string, tag string) (int64, error) { + var total int64 + var err error + if q != "" { + err = r.DB.Model(&models.QuizPaper{}).Where("title LIKE ? OR category LIKE ? OR tag LIKE ?", "%"+q+"%", "%"+q+"%", "%"+q+"%").Count(&total).Error + } else if tag != "" { + err = r.DB.Model(&models.QuizPaper{}).Where("tag LIKE ?", "%"+tag+"%").Count(&total).Error + } else { + err = r.DB.Model(&models.QuizPaper{}).Count(&total).Error + } + + return total, err +} + +func (r *quizPaperRepository) Find(id int64) (*models.QuizPaper, error) { + var quizPaper *models.QuizPaper + err := r.DB.Where("id = ?", id).First(&quizPaper).Error + return quizPaper, err +} + +func (r *quizPaperRepository) Create(quizPaper *models.QuizPaper) (*models.QuizPaper, error) { + err := r.DB.Create(&quizPaper).Error + return quizPaper, err +} + +func (r *quizPaperRepository) Update(quizPaper *models.QuizPaper) (*models.QuizPaper, error) { + var row *models.QuizPaper + if err := r.DB.Where("id=?", quizPaper.ID).First(&row).Error; err != nil { + return nil, err + } + + err := r.DB.Model(&row).Select("*").Updates(&quizPaper).Error + return row, err +} + +func (r *quizPaperRepository) Delete(id string) error { + var quizPaper *models.QuizPaper + if err := r.DB.Where("id=?", id).First(&quizPaper).Error; err != nil { + return err + } + err := r.DB.Delete(&quizPaper).Error + return err +} diff --git a/internal/repositories/token.go b/internal/repositories/token.go new file mode 100644 index 0000000..04d0287 --- /dev/null +++ b/internal/repositories/token.go @@ -0,0 +1,79 @@ +package repositories + +import ( + "fmt" + config "learnsteam/cslms-api/configs" + "learnsteam/cslms-api/internal/models" + + "github.com/golang-jwt/jwt/v5" + "gorm.io/gorm" +) + +type tokenRepository struct { + DB *gorm.DB +} + +func NewTokenRepository(db *gorm.DB) TokenRepository { + return &tokenRepository{ + DB: db, + } +} + +type TokenRepository interface { + Generate(int64, int64, string) (string, error) + Find(int64) (*models.Token, error) + FindByRefreshToken(int64, string) (*models.Token, error) + Create(*models.Token) (*models.Token, error) + Update(*models.Token) (*models.Token, error) + Delete(string) error +} + +func (s *tokenRepository) Generate(user_id int64, expire_at int64, role string) (string, error) { + fmt.Println(user_id, role) + claims := jwt.MapClaims{} + claims["authorized"] = true + claims["sub"] = user_id + claims["exp"] = expire_at + claims["role"] = role + at := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + token, err := at.SignedString([]byte(config.SECRET_KEY)) + + fmt.Println(token) + return token, err +} + +func (r *tokenRepository) Find(id int64) (*models.Token, error) { + var token *models.Token + err := r.DB.Where("id = ?", id).First(&token).Error + return token, err +} + +func (r *tokenRepository) FindByRefreshToken(user_id int64, refreshToken string) (*models.Token, error) { + var token *models.Token + err := r.DB.Where("user_id = ? AND refresh_token = ?", user_id, refreshToken).First(&token).Error + return token, err +} + +func (r *tokenRepository) Create(token *models.Token) (*models.Token, error) { + err := r.DB.Create(&token).Error + return token, err +} + +func (r *tokenRepository) Update(token *models.Token) (*models.Token, error) { + var row *models.Token + if err := r.DB.Where("id=?", token.ID).First(&row).Error; err != nil { + return nil, err + } + + err := r.DB.Model(&row).Select("*").Updates(&token).Error + return row, err +} + +func (r *tokenRepository) Delete(id string) error { + var token *models.Token + if err := r.DB.Where("id=?", id).First(&token).Error; err != nil { + return err + } + err := r.DB.Delete(&token).Error + return err +} diff --git a/internal/repositories/user.go b/internal/repositories/user.go new file mode 100644 index 0000000..a0bdb6c --- /dev/null +++ b/internal/repositories/user.go @@ -0,0 +1,90 @@ +package repositories + +import ( + "fmt" + "learnsteam/cslms-api/internal/models" + + "gorm.io/gorm" +) + +type userRepository struct { + DB *gorm.DB +} + +func NewUserRepository(db *gorm.DB) UserRepository { + return &userRepository{ + DB: db, + } +} + +type UserRepository interface { + List(string, int, int) (*[]models.User, error) + Total(string) (int64, error) + Find(int64) (*models.User, error) + FindByUsername(string) (*models.User, error) + Create(*models.User) (*models.User, error) + Update(*models.User) (*models.User, error) + Delete(string) error +} + +func (r *userRepository) List(q string, page int, limit int) (*[]models.User, error) { + var users *[]models.User + var err error + offset := limit * (page - 1) + fmt.Println("q", q) + if q != "" { + err = r.DB.Offset(offset).Limit(limit).Order("last_name ASC, first_name ASC").Where("user_role = ?", "member").Where("name LIKE ? OR user_name LIKE ?", "%"+q+"%", "%"+q+"%").Find(&users).Error + } else { + fmt.Println(" query") + err = r.DB.Offset(offset).Limit(limit).Order("last_name ASC, first_name ASC").Where("user_role = ?", "member").Find(&users).Error + } + return users, err +} + +func (r *userRepository) Total(q string) (int64, error) { + var total int64 + var err error + if q != "" { + err = r.DB.Model(&models.User{}).Where("user_role = ?", "member").Where("name LIKE ? OR user_name LIKE ?", "%"+q+"%", "%"+q+"%").Count(&total).Error + } else { + err = r.DB.Model(&models.User{}).Where("user_role = ?", "member").Count(&total).Error + } + + return total, err +} + +func (r *userRepository) Find(id int64) (*models.User, error) { + var user *models.User + err := r.DB.Where("id = ?", id).First(&user).Error + return user, err +} + +func (r *userRepository) FindByUsername(username string) (*models.User, error) { + var user *models.User + err := r.DB.Where("user_name = ?", username).First(&user).Error + return user, err +} + +func (r *userRepository) Create(user *models.User) (*models.User, error) { + err := r.DB.Create(&user).Error + return user, err +} + +func (r *userRepository) Update(user *models.User) (*models.User, error) { + var row *models.User + if err := r.DB.Where("id=?", user.ID).First(&row).Error; err != nil { + return nil, err + } + + err := r.DB.Model(&row).Select("*").Updates(&user).Error + return row, err +} + +func (r *userRepository) Delete(id string) error { + var user *models.User + if err := r.DB.Where("id=?", id).First(&user).Error; err != nil { + return err + } + err := r.DB.Delete(&user).Error + return err +} diff --git a/internal/repositories/userquiz.go b/internal/repositories/userquiz.go new file mode 100644 index 0000000..30eca8a --- /dev/null +++ b/internal/repositories/userquiz.go @@ -0,0 +1,89 @@ +package repositories + +import ( + "fmt" + "learnsteam/cslms-api/internal/models" + + "gorm.io/gorm" +) + +type userQuizRepository struct { + DB *gorm.DB +} + +func NewUserQuizRepository(db *gorm.DB) UserQuizRepository { + return &userQuizRepository{ + DB: db, + } +} + +type UserQuizRepository interface { + List(int64, int, int) (*[]models.UserQuiz, error) + Total(int64) (int64, error) + + Find(int64) (*models.UserQuiz, error) + Create(*models.UserQuiz) (*models.UserQuiz, error) + Insert([]models.UserQuiz) ([]models.UserQuiz, error) + Update(*models.UserQuiz) (*models.UserQuiz, error) + Delete(int64) error +} + +func (r *userQuizRepository) List(user_quiz_paper_id int64, page int, limit int) (*[]models.UserQuiz, error) { + var rows *[]models.UserQuiz + var err error + offset := limit * (page - 1) + if user_quiz_paper_id > 0 { + err = r.DB.Offset(offset).Limit(limit).Order("vol_no ASC").Where("user_quiz_paper_id = ?", user_quiz_paper_id).Find(&rows).Error + } else { + err = r.DB.Offset(offset).Limit(limit).Find(&rows).Error + } + return rows, err +} + +func (r *userQuizRepository) Total(user_quiz_paper_id int64) (int64, error) { + var total int64 + var err error + if user_quiz_paper_id > 0 { + err = r.DB.Model(&models.UserQuiz{}).Where("user_quiz_paper_id = ?", user_quiz_paper_id).Count(&total).Error + } else { + err = r.DB.Model(&models.UserQuiz{}).Count(&total).Error + } + + return total, err +} + +func (r *userQuizRepository) Find(id int64) (*models.UserQuiz, error) { + var row *models.UserQuiz + err := r.DB.Where("id = ?", id).First(&row).Error + return row, err +} + +func (r *userQuizRepository) Create(row *models.UserQuiz) (*models.UserQuiz, error) { + err := r.DB.Create(&row).Error + return row, err +} + +func (r *userQuizRepository) Insert(items []models.UserQuiz) ([]models.UserQuiz, error) { + fmt.Println(items) + err := r.DB.Create(&items).Error + return items, err +} + +func (r *userQuizRepository) Update(quiz *models.UserQuiz) (*models.UserQuiz, error) { + var row *models.UserQuiz + if err := r.DB.Where("id=?", quiz.ID).First(&row).Error; err != nil { + return nil, err + } + + err := r.DB.Model(&row).Select("*").Updates(&quiz).Error + return row, err +} + +func (r *userQuizRepository) Delete(id int64) error { + var row *models.UserQuiz + if err := r.DB.Where("id=?", id).First(&row).Error; err != nil { + return err + } + err := r.DB.Delete(&row).Error + return err +} diff --git a/internal/repositories/userquizpaper.go b/internal/repositories/userquizpaper.go new file mode 100644 index 0000000..b04c304 --- /dev/null +++ b/internal/repositories/userquizpaper.go @@ -0,0 +1,87 @@ +package repositories + +import ( + "learnsteam/cslms-api/internal/models" + + "gorm.io/gorm" +) + +type userQuizPaperRepository struct { + DB *gorm.DB +} + +func NewUserQuizPaperRepository(db *gorm.DB) UserQuizPaperRepository { + return &userQuizPaperRepository{ + DB: db, + } +} + +type UserQuizPaperRepository interface { + List(string, int, int) (*[]models.UserQuizPaper, error) + Total(string) (int64, error) + + Find(int64) (*models.UserQuizPaper, error) + Create(*models.UserQuizPaper) (*models.UserQuizPaper, error) + Insert([]models.UserQuizPaper) ([]models.UserQuizPaper, error) + Update(*models.UserQuizPaper) (*models.UserQuizPaper, error) + Delete(string) error +} + +func (r *userQuizPaperRepository) List(q string, page int, limit int) (*[]models.UserQuizPaper, error) { + var userQuizPapers *[]models.UserQuizPaper + 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(&userQuizPapers).Error + } else { + err = r.DB.Offset(offset).Limit(limit).Order("created_at DESC").Find(&userQuizPapers).Error + } + return userQuizPapers, err +} + +func (r *userQuizPaperRepository) Total(q string) (int64, error) { + var total int64 + var err error + if q != "" { + err = r.DB.Model(&models.UserQuizPaper{}).Where("subject LIKE ? OR program LIKE ? OR name LIKE ?", "%"+q+"%", "%"+q+"%", "%"+q+"%").Count(&total).Error + } else { + err = r.DB.Model(&models.UserQuizPaper{}).Count(&total).Error + } + + return total, err +} + +func (r *userQuizPaperRepository) Find(id int64) (*models.UserQuizPaper, error) { + var userQuizPaper *models.UserQuizPaper + err := r.DB.Where("id = ?", id).First(&userQuizPaper).Error + return userQuizPaper, err +} + +func (r *userQuizPaperRepository) Create(userQuizPaper *models.UserQuizPaper) (*models.UserQuizPaper, error) { + err := r.DB.Create(&userQuizPaper).Error + return userQuizPaper, err +} + +func (r *userQuizPaperRepository) Insert(items []models.UserQuizPaper) ([]models.UserQuizPaper, error) { + err := r.DB.Create(&items).Error + return items, err +} + +func (r *userQuizPaperRepository) Update(userQuizPaper *models.UserQuizPaper) (*models.UserQuizPaper, error) { + var row *models.UserQuizPaper + if err := r.DB.Where("id=?", userQuizPaper.ID).First(&row).Error; err != nil { + return nil, err + } + + err := r.DB.Model(&row).Select("*").Updates(&userQuizPaper).Error + return row, err +} + +func (r *userQuizPaperRepository) Delete(id string) error { + var userQuizPaper *models.UserQuizPaper + if err := r.DB.Where("id=?", id).First(&userQuizPaper).Error; err != nil { + return err + } + err := r.DB.Delete(&userQuizPaper).Error + return err +} diff --git a/internal/routers/auth.go b/internal/routers/auth.go new file mode 100644 index 0000000..1ded136 --- /dev/null +++ b/internal/routers/auth.go @@ -0,0 +1,53 @@ +package routers + +import ( + "learnsteam/cslms-api/internal/controllers" + "learnsteam/cslms-api/internal/repositories" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func InitAuthRouter(db *gorm.DB, router *gin.Engine) { + r := NewAuthRouter(db, router) + r.SetAuthRouter() +} + +type AuthRouter interface { + SetRouter(db *gorm.DB, router *gin.Engine) +} + +type authRouter struct { + db *gorm.DB + userRepository repositories.UserRepository + tokenRepository repositories.TokenRepository + service services.AuthService + tokenService services.TokenService + controller controllers.AuthController + router *gin.Engine +} + +func NewAuthRouter(db *gorm.DB, router *gin.Engine) *authRouter { + userRepository := repositories.NewUserRepository(db) + tokenRepository := repositories.NewTokenRepository(db) + service := services.NewAuthService(userRepository, tokenRepository) + tokenService := services.NewTokenService(tokenRepository) + controller := controllers.NewAuthController(service, tokenService) + + return &authRouter{ + db: db, + userRepository: userRepository, + tokenRepository: tokenRepository, + service: service, + tokenService: tokenService, + controller: controller, + router: router, + } +} + +func (r *authRouter) SetAuthRouter() { + group := r.router.Group("/auth") + group.POST("login", r.controller.Login) + group.POST("register", r.controller.Register) +} diff --git a/internal/routers/center.go b/internal/routers/center.go new file mode 100644 index 0000000..20a98f5 --- /dev/null +++ b/internal/routers/center.go @@ -0,0 +1,53 @@ +package routers + +import ( + "learnsteam/cslms-api/internal/controllers" + "learnsteam/cslms-api/internal/middleware" + "learnsteam/cslms-api/internal/repositories" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func InitCenterRouter(db *gorm.DB, router *gin.Engine) { + r := NewCenterRouter(db, router) + r.SetCenterRouter() +} + +type CenterRouter interface { + SetRouter(db *gorm.DB, router *gin.Engine) +} + +type centerRouter struct { + db *gorm.DB + repository repositories.CenterRepository + service services.CenterService + controller controllers.CenterController + router *gin.Engine +} + +func NewCenterRouter(db *gorm.DB, router *gin.Engine) *centerRouter { + repository := repositories.NewCenterRepository(db) + tokenRepository := repositories.NewTokenRepository(db) + service := services.NewCenterService(repository, tokenRepository) + + tokenService := services.NewTokenService(tokenRepository) + controller := controllers.NewCenterController(service, tokenService) + + return ¢erRouter{ + db: db, + repository: repository, + service: service, + controller: controller, + router: router, + } +} + +func (r *centerRouter) SetCenterRouter() { + group := r.router.Group("/center") + group.GET("", middleware.Auth("admin"), r.controller.List) + group.GET("/:id", middleware.Auth("admin"), r.controller.Find) + group.POST("", middleware.Auth("admin"), r.controller.Create) + group.PUT("/:id", middleware.Auth("admin"), r.controller.Update) +} diff --git a/internal/routers/quiz.go b/internal/routers/quiz.go new file mode 100644 index 0000000..a740b05 --- /dev/null +++ b/internal/routers/quiz.go @@ -0,0 +1,50 @@ +package routers + +import ( + "learnsteam/cslms-api/internal/controllers" + "learnsteam/cslms-api/internal/middleware" + "learnsteam/cslms-api/internal/repositories" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func InitQuizRouter(db *gorm.DB, router *gin.Engine) { + r := NewQuizRouter(db, router) + r.SetQuizRouter() +} + +type QuizRouter interface { + SetRouter(db *gorm.DB, router *gin.Engine) +} + +type quizRouter struct { + db *gorm.DB + repository repositories.QuizRepository + service services.QuizService + controller controllers.QuizController + router *gin.Engine +} + +func NewQuizRouter(db *gorm.DB, router *gin.Engine) *quizRouter { + repository := repositories.NewQuizRepository(db) + service := services.NewQuizService(repository) + controller := controllers.NewQuizController(service) + + return &quizRouter{ + db: db, + repository: repository, + service: service, + controller: controller, + router: router, + } +} + +func (r *quizRouter) SetQuizRouter() { + group := r.router.Group("/quiz") + group.GET("", middleware.Auth("admin"), r.controller.List) + group.GET("/:id", middleware.Auth("admin"), r.controller.Find) + group.POST("", middleware.Auth("admin"), r.controller.Create) + group.PUT("/:id", middleware.Auth("admin"), r.controller.Update) +} diff --git a/internal/routers/quizpaper.go b/internal/routers/quizpaper.go new file mode 100644 index 0000000..49a6c85 --- /dev/null +++ b/internal/routers/quizpaper.go @@ -0,0 +1,57 @@ +package routers + +import ( + "learnsteam/cslms-api/internal/controllers" + "learnsteam/cslms-api/internal/middleware" + "learnsteam/cslms-api/internal/repositories" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func InitQuizPaperRouter(db *gorm.DB, router *gin.Engine) { + r := NewQuizPaperRouter(db, router) + r.SetQuizPaperRouter() +} + +type QuizPaperRouter interface { + SetRouter(db *gorm.DB, router *gin.Engine) +} + +type quizPaperRouter struct { + db *gorm.DB + repository repositories.QuizPaperRepository + quizRepository repositories.QuizRepository + service services.QuizPaperService + quizService services.QuizService + controller controllers.QuizPaperController + router *gin.Engine +} + +func NewQuizPaperRouter(db *gorm.DB, router *gin.Engine) *quizPaperRouter { + repository := repositories.NewQuizPaperRepository(db) + quizRepository := repositories.NewQuizRepository(db) + service := services.NewQuizPaperService(repository) + quizService := services.NewQuizService(quizRepository) + controller := controllers.NewQuizPaperController(service, quizService) + + return &quizPaperRouter{ + db: db, + repository: repository, + quizRepository: quizRepository, + service: service, + quizService: quizService, + controller: controller, + router: router, + } +} + +func (r *quizPaperRouter) SetQuizPaperRouter() { + group := r.router.Group("/quizpaper") + group.GET("", middleware.Auth("admin"), r.controller.List) + group.GET("/:id", middleware.Auth("admin"), r.controller.Find) + group.POST("", middleware.Auth("admin"), r.controller.Create) + group.PUT("/:id", middleware.Auth("admin"), r.controller.Update) + group.POST("/:id/copy", middleware.Auth("admin"), r.controller.Copy) +} diff --git a/internal/routers/router.go b/internal/routers/router.go new file mode 100644 index 0000000..3997222 --- /dev/null +++ b/internal/routers/router.go @@ -0,0 +1,33 @@ +package routers + +import ( + "learnsteam/cslms-api/internal/database" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" +) + +var Router *gin.Engine + +func Init() { + gin.SetMode(gin.ReleaseMode) + Router = gin.Default() + config := cors.DefaultConfig() + config.AllowOrigins = []string{"http://127.0.0.1:3000", "http://localhost:3000", "http://localhost:3030", "https://learnsteam-quiz.jongyeob.com"} + config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "PATCH"} + config.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type"} + Router.Use(cors.New(config)) + + maindb := database.GetDB() + + InitAuthRouter(maindb, Router) + InitCenterRouter(maindb, Router) + InitQuizPaperRouter(maindb, Router) + InitQuizRouter(maindb, Router) + InitTokenRouter(maindb, Router) + InitUserQuizPaperRouter(maindb, Router) + InitUserQuizRouter(maindb, Router) + InitUserRouter(maindb, Router) + + InitSwaggerRouter(Router) +} diff --git a/internal/routers/swagger.go b/internal/routers/swagger.go new file mode 100644 index 0000000..c15df41 --- /dev/null +++ b/internal/routers/swagger.go @@ -0,0 +1,37 @@ +package routers + +import ( + "learnsteam/cslms-api/internal/controllers" + + "github.com/gin-gonic/gin" + + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" +) + +func InitSwaggerRouter(router *gin.Engine) { + r := NewSwaggerRouter(router) + r.SetSwaggerRouter() +} + +type SwaggerRouter interface { + SetRouter(router *gin.Engine) +} + +type swaggerRouter struct { + router *gin.Engine + controller controllers.SwaggerController +} + +func NewSwaggerRouter(router *gin.Engine) *swaggerRouter { + controller := controllers.NewSwaggerController() + return &swaggerRouter{ + router: router, + controller: controller, + } +} + +func (r *swaggerRouter) SetSwaggerRouter() { + group := r.router.Group("/swagger") + group.GET("*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) +} diff --git a/internal/routers/token.go b/internal/routers/token.go new file mode 100644 index 0000000..4106fb4 --- /dev/null +++ b/internal/routers/token.go @@ -0,0 +1,46 @@ +package routers + +import ( + "learnsteam/cslms-api/internal/controllers" + "learnsteam/cslms-api/internal/repositories" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func InitTokenRouter(db *gorm.DB, router *gin.Engine) { + r := NewTokenRouter(db, router) + r.SetTokenRouter() +} + +type TokenRouter interface { + SetRouter(db *gorm.DB, router *gin.Engine) +} + +type tokenRouter struct { + db *gorm.DB + repository repositories.TokenRepository + service services.TokenService + controller controllers.TokenController + router *gin.Engine +} + +func NewTokenRouter(db *gorm.DB, router *gin.Engine) *tokenRouter { + repository := repositories.NewTokenRepository(db) + service := services.NewTokenService(repository) + controller := controllers.NewTokenController(service) + + return &tokenRouter{ + db: db, + repository: repository, + service: service, + controller: controller, + router: router, + } +} + +func (r *tokenRouter) SetTokenRouter() { + group := r.router.Group("/token") + group.POST("refresh", r.controller.Refresh) +} diff --git a/internal/routers/user.go b/internal/routers/user.go new file mode 100644 index 0000000..301677c --- /dev/null +++ b/internal/routers/user.go @@ -0,0 +1,51 @@ +package routers + +import ( + "learnsteam/cslms-api/internal/controllers" + "learnsteam/cslms-api/internal/middleware" + "learnsteam/cslms-api/internal/repositories" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func InitUserRouter(db *gorm.DB, router *gin.Engine) { + r := NewUserRouter(db, router) + r.SetUserRouter() +} + +type UserRouter interface { + SetRouter(db *gorm.DB, router *gin.Engine) +} + +type userRouter struct { + db *gorm.DB + repository repositories.UserRepository + service services.UserService + controller controllers.UserController + router *gin.Engine +} + +func NewUserRouter(db *gorm.DB, router *gin.Engine) *userRouter { + repository := repositories.NewUserRepository(db) + tokenRepository := repositories.NewTokenRepository(db) + service := services.NewUserService(repository, tokenRepository) + + tokenService := services.NewTokenService(tokenRepository) + controller := controllers.NewUserController(service, tokenService) + + return &userRouter{ + db: db, + repository: repository, + service: service, + controller: controller, + router: router, + } +} + +func (r *userRouter) SetUserRouter() { + group := r.router.Group("/user") + group.GET("", middleware.Auth("admin"), r.controller.List) + group.GET("/:id", middleware.Auth("admin"), r.controller.Find) +} diff --git a/internal/routers/userquiz.go b/internal/routers/userquiz.go new file mode 100644 index 0000000..b80a649 --- /dev/null +++ b/internal/routers/userquiz.go @@ -0,0 +1,53 @@ +package routers + +import ( + "learnsteam/cslms-api/internal/controllers" + "learnsteam/cslms-api/internal/middleware" + "learnsteam/cslms-api/internal/repositories" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func InitUserQuizRouter(db *gorm.DB, router *gin.Engine) { + r := NewUserQuizRouter(db, router) + r.SetUserQuizRouter() +} + +type UserQuizRouter interface { + SetRouter(db *gorm.DB, router *gin.Engine) +} + +type userQuizRouter struct { + db *gorm.DB + repository repositories.UserQuizRepository + quizRepository repositories.QuizRepository + service services.UserQuizService + controller controllers.UserQuizController + router *gin.Engine +} + +func NewUserQuizRouter(db *gorm.DB, router *gin.Engine) *userQuizRouter { + repository := repositories.NewUserQuizRepository(db) + quizRepostory := repositories.NewQuizRepository(db) + service := services.NewUserQuizService(repository, quizRepostory) + controller := controllers.NewUserQuizController(service) + + return &userQuizRouter{ + db: db, + repository: repository, + quizRepository: quizRepostory, + service: service, + controller: controller, + router: router, + } +} + +func (r *userQuizRouter) SetUserQuizRouter() { + group := r.router.Group("/userquiz") + group.GET("", middleware.Auth("admin"), r.controller.List) + group.GET("/:id", middleware.Auth("admin"), r.controller.Find) + group.POST("", middleware.Auth("admin"), r.controller.Create) + group.PUT("/:id", middleware.Auth("admin"), r.controller.Update) +} diff --git a/internal/routers/userquizpaper.go b/internal/routers/userquizpaper.go new file mode 100644 index 0000000..522b127 --- /dev/null +++ b/internal/routers/userquizpaper.go @@ -0,0 +1,67 @@ +package routers + +import ( + "learnsteam/cslms-api/internal/controllers" + "learnsteam/cslms-api/internal/middleware" + "learnsteam/cslms-api/internal/repositories" + "learnsteam/cslms-api/internal/services" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +func InitUserQuizPaperRouter(db *gorm.DB, router *gin.Engine) { + r := NewUserQuizPaperRouter(db, router) + r.SetUserQuizPaperRouter() +} + +type UserQuizPaperRouter interface { + SetRouter(db *gorm.DB, router *gin.Engine) +} + +type userQuizPaperRouter struct { + db *gorm.DB + repository repositories.UserQuizPaperRepository + service services.UserQuizPaperService + controller controllers.UserQuizPaperController + // quizPaperRepository repositories.QuizPaperRepository + // userRepository repositories.UserRepository + + // quizPaperService services.QuizPaperService + // userService services.UserService + + router *gin.Engine +} + +func NewUserQuizPaperRouter(db *gorm.DB, router *gin.Engine) *userQuizPaperRouter { + repository := repositories.NewUserQuizPaperRepository(db) + quizRepository := repositories.NewQuizRepository(db) + quizPaperRepository := repositories.NewQuizPaperRepository(db) + tokenRepository := repositories.NewTokenRepository(db) + userRepository := repositories.NewUserRepository(db) + userQuizRepository := repositories.NewUserQuizRepository(db) + + service := services.NewUserQuizPaperService(repository) + quizPaperService := services.NewQuizPaperService(quizPaperRepository) + userService := services.NewUserService(userRepository, tokenRepository) + userQuizService := services.NewUserQuizService(userQuizRepository, quizRepository) + + controller := controllers.NewUserQuizPaperController(service, userService, quizPaperService, userQuizService) + + return &userQuizPaperRouter{ + db: db, + repository: repository, + service: service, + controller: controller, + router: router, + } +} + +func (r *userQuizPaperRouter) SetUserQuizPaperRouter() { + group := r.router.Group("/userquizpaper") + group.GET("", middleware.Auth("admin"), r.controller.List) + group.GET("/:id", middleware.Auth("admin"), r.controller.Find) + group.POST("", middleware.Auth("admin"), r.controller.Create) + group.PUT("/:id", middleware.Auth("admin"), r.controller.Update) + group.PATCH("/:id", middleware.Auth("admin"), r.controller.Patch) +} diff --git a/internal/services/auth.go b/internal/services/auth.go new file mode 100644 index 0000000..c43042a --- /dev/null +++ b/internal/services/auth.go @@ -0,0 +1,72 @@ +package services + +import ( + "errors" + "fmt" + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/repositories" + "time" + + "github.com/google/uuid" + "golang.org/x/crypto/bcrypt" +) + +type authService struct { + userRepository repositories.UserRepository + tokenRepository repositories.TokenRepository +} + +type AuthService interface { + Register(*models.RegisterRequest) (*models.User, error) + Login(*models.LoginRequest) (*models.User, error) +} + +func NewAuthService(userRepository repositories.UserRepository, tokenRepository repositories.TokenRepository) AuthService { + return &authService{ + userRepository: userRepository, + tokenRepository: tokenRepository, + } +} + +func (s *authService) Register(request *models.RegisterRequest) (*models.User, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(request.Password), 10) + if err != nil { + return nil, errors.New("fail to hash password") + } + + // Create the user + newUser := models.User{ + GUID: uuid.NewString(), + FirstName: request.FirstName, + LastName: request.LastName, + Username: request.Username, + Password: string(hash), + Gender: request.Gender, + Phone: request.Phone, + UserRole: request.UserRole, + Image: request.Image, + RegisterAt: time.Now(), + } + + fmt.Println(newUser) + + user, err := s.userRepository.Create(&newUser) + if err != nil { + return nil, errors.New("fail to create user") + } + + return user, err +} + +func (s *authService) Login(request *models.LoginRequest) (*models.User, error) { + user, err := s.userRepository.FindByUsername(request.Username) + if err != nil || user == nil { + return nil, errors.New("invalid user or password") + } + + err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(request.Password)) + if err != nil { + return nil, errors.New("invalid user or password") + } + return user, nil +} diff --git a/internal/services/center.go b/internal/services/center.go new file mode 100644 index 0000000..68621af --- /dev/null +++ b/internal/services/center.go @@ -0,0 +1,64 @@ +package services + +import ( + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/repositories" +) + +type centerService struct { + repository repositories.CenterRepository + tokenRepository repositories.TokenRepository +} + +type CenterService interface { + List(string, int, int) (*[]models.Center, error) + Total(string) (int64, error) + + Find(int64) (*models.Center, error) + FindByTitle(string) (*models.Center, error) + + Create(*models.Center) (*models.Center, error) + Update(*models.Center) (*models.Center, error) + Delete(int64) error +} + +func NewCenterService(repository repositories.CenterRepository, tokenRepository repositories.TokenRepository) CenterService { + return ¢erService{ + repository: repository, + tokenRepository: tokenRepository, + } +} + +func (s *centerService) List(q string, page int, limit int) (*[]models.Center, error) { + return s.repository.List(q, page, limit) +} + +func (s *centerService) Total(q string) (int64, error) { + return s.repository.Total(q) +} + +func (s *centerService) Find(id int64) (*models.Center, error) { + return s.repository.Find(id) +} + +func (s *centerService) FindByTitle(centername string) (*models.Center, error) { + return s.repository.FindByTitle(centername) +} + +func (s *centerService) Create(center *models.Center) (*models.Center, error) { + result, err := s.repository.Create(center) + + return result, err +} + +func (s *centerService) Update(center *models.Center) (*models.Center, error) { + result, err := s.repository.Update(center) + + return result, err +} + +func (s *centerService) Delete(id int64) error { + err := s.repository.Delete(id) + + return err +} diff --git a/internal/services/quiz.go b/internal/services/quiz.go new file mode 100644 index 0000000..c355b40 --- /dev/null +++ b/internal/services/quiz.go @@ -0,0 +1,85 @@ +package services + +import ( + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/repositories" + "time" + + "github.com/google/uuid" +) + +type quizService struct { + repository repositories.QuizRepository +} + +type QuizService interface { + List(int64, int, int) (*[]models.Quiz, error) + Total(int64) (int64, error) + + Find(int64) (*models.Quiz, error) + Create(*models.Quiz) (*models.Quiz, error) + Update(*models.Quiz) (*models.Quiz, error) + Delete(int64) error +} + +func NewQuizService(repository repositories.QuizRepository) QuizService { + return &quizService{ + repository: repository, + } +} + +func (s *quizService) List(quiz_paper_id int64, page int, limit int) (*[]models.Quiz, error) { + return s.repository.List(quiz_paper_id, page, limit) +} + +func (s *quizService) Total(quiz_paper_id int64) (int64, error) { + return s.repository.Total(quiz_paper_id) +} + +func (s *quizService) Find(id int64) (*models.Quiz, error) { + return s.repository.Find(id) +} + +func (s *quizService) Create(quiz *models.Quiz) (*models.Quiz, error) { + result, err := s.repository.Create(quiz) + + return result, err +} + +func (s *quizService) Update(quiz *models.Quiz) (*models.Quiz, error) { + result, err := s.repository.Update(quiz) + + return result, err +} + +func (s *quizService) Delete(id int64) error { + err := s.repository.Delete(id) + + return err +} + +func (s *quizService) Generate(quizpaper *models.QuizPaper) ([]models.Quiz, error) { + templates, err := s.repository.List(quizpaper.ID, 1, 1000) + if err != nil { + return nil, err + } + + var quizzes []models.Quiz + for _, q := range *templates { + quiz := models.Quiz{ + GUID: uuid.NewString(), + CenterID: quizpaper.CenterID, + QuizPaperID: quizpaper.ID, + No: q.No, + QuestionType: q.QuestionType, + Question: q.Question, + Content: q.Content, + Answer: q.Answer, + UpdatedAt: time.Now(), + CreatedAt: time.Now(), + } + quizzes = append(quizzes, quiz) + } + + return quizzes, nil +} diff --git a/internal/services/quizpaper.go b/internal/services/quizpaper.go new file mode 100644 index 0000000..d888a73 --- /dev/null +++ b/internal/services/quizpaper.go @@ -0,0 +1,56 @@ +package services + +import ( + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/repositories" +) + +type quizPaperService struct { + repository repositories.QuizPaperRepository +} + +type QuizPaperService interface { + List(string, string, int, int) (*[]models.QuizPaper, error) + Total(string, string) (int64, error) + + Find(int64) (*models.QuizPaper, error) + Create(*models.QuizPaper) (*models.QuizPaper, error) + Update(*models.QuizPaper) (*models.QuizPaper, error) + Delete(string) error +} + +func NewQuizPaperService(repository repositories.QuizPaperRepository) QuizPaperService { + return &quizPaperService{ + repository: repository, + } +} + +func (s *quizPaperService) List(q string, tag string, page int, limit int) (*[]models.QuizPaper, error) { + return s.repository.List(q, tag, page, limit) +} + +func (s *quizPaperService) Total(q string, tag string) (int64, error) { + return s.repository.Total(q, tag) +} + +func (s *quizPaperService) Find(id int64) (*models.QuizPaper, error) { + return s.repository.Find(id) +} + +func (s *quizPaperService) Create(quizPaper *models.QuizPaper) (*models.QuizPaper, error) { + result, err := s.repository.Create(quizPaper) + + return result, err +} + +func (s *quizPaperService) Update(quizPaper *models.QuizPaper) (*models.QuizPaper, error) { + result, err := s.repository.Update(quizPaper) + + return result, err +} + +func (s *quizPaperService) Delete(id string) error { + err := s.repository.Delete(id) + + return err +} diff --git a/internal/services/token.go b/internal/services/token.go new file mode 100644 index 0000000..35a27db --- /dev/null +++ b/internal/services/token.go @@ -0,0 +1,200 @@ +package services + +import ( + "errors" + "fmt" + "strings" + "time" + + config "learnsteam/cslms-api/configs" + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/repositories" + + "github.com/golang-jwt/jwt/v5" +) + +type tokenService struct { + repository repositories.TokenRepository +} + +type TokenService interface { + Find(int64) (*models.Token, error) + Create(int64, string) (*models.Token, error) + Update(*models.Token) (*models.Token, error) + Delete(string) error + + Refresh(string) (*models.Token, error) + + Generate(string, int64, string) (string, error) + Verify(tokenString string) (*jwt.Token, error) + + GetJwtToken(string) (*jwt.Token, error) + ExtractTokenString(string) string + VerifyTokenString(string) (*jwt.Token, error) + ValidToken(*jwt.Token) (bool, error) +} + +func NewTokenService(repository repositories.TokenRepository) TokenService { + return &tokenService{ + repository: repository, + } +} + +func (s *tokenService) Find(id int64) (*models.Token, error) { + return s.repository.Find(id) +} + +func (s *tokenService) Create(user_id int64, role string) (*models.Token, error) { + expiredAt := time.Now().Add(time.Hour * 24 * 30) + accessToken, err := s.repository.Generate(user_id, expiredAt.Unix(), role) + if err != nil { + return nil, err + } + + refreshExpiredAt := time.Now().Add(time.Hour * 24 * 90) + refreshToken, err := s.repository.Generate(user_id, refreshExpiredAt.Unix(), role) + if err != nil { + return nil, err + } + + newToken := &models.Token{ + UserID: user_id, + Token: accessToken, + RefreshToken: refreshToken, + Status: "on", + EndingAt: expiredAt, + RegisterAt: time.Now(), + } + + token, err := s.repository.Create(newToken) + + return token, err +} + +func (s *tokenService) Update(token *models.Token) (*models.Token, error) { + return s.repository.Update(token) +} + +func (s *tokenService) Delete(id string) error { + return s.repository.Delete(id) +} + +func (s *tokenService) Verify(tokenString string) (*jwt.Token, error) { + jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) { + if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", jwtToken.Header["alg"]) + } + return []byte(config.SECRET_KEY), nil + }) + + return jwtToken, err +} + +func (s *tokenService) Generate(user_id string, expire_at int64, role string) (string, error) { + claims := jwt.MapClaims{} + claims["authorized"] = true + claims["sub"] = user_id + claims["exp"] = expire_at + claims["role"] = role + at := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + token, err := at.SignedString([]byte(config.SECRET_KEY)) + + return token, err +} + +func (s *tokenService) GetJwtToken(tokenString string) (*jwt.Token, error) { + jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) { + if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", jwtToken.Header["alg"]) + } + return []byte(config.SECRET_KEY), nil + }) + return jwtToken, err +} + +func (s *tokenService) ExtractTokenString(authorization string) string { + strArr := strings.Split(authorization, " ") + if len(strArr) == 2 { + return strArr[1] + } + return "" +} + +func (s *tokenService) VerifyTokenString(tokenString string) (*jwt.Token, error) { + jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) { + if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", jwtToken.Header["alg"]) + } + return []byte(config.SECRET_KEY), nil + }) + + if err != nil { + return nil, err + } + + return jwtToken, nil +} + +func (s *tokenService) ValidToken(jwtToken *jwt.Token) (bool, error) { + if jwtToken == nil { + return false, fmt.Errorf("no token") + } + return jwtToken.Valid, nil +} + +func (s *tokenService) Refresh(refreshToken string) (*models.Token, error) { + fmt.Println("refresh_token", refreshToken) + + jwtToken, err := jwt.Parse(refreshToken, func(jwtToken *jwt.Token) (interface{}, error) { + if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", jwtToken.Header["alg"]) + } + return []byte(config.SECRET_KEY), nil + }) + + if err != nil { + fmt.Println("refresh token is not valid") + } else { + + fmt.Println("refresh token is valid") + } + + if err != nil { + fmt.Println("error", err.Error()) + return nil, err + } + + claims, ok := jwtToken.Claims.(jwt.MapClaims) + if !ok || !jwtToken.Valid { + return nil, errors.New("refresh token is invalid") + } + + sub := claims["sub"].(int64) + if err != nil { + return nil, errors.New("wrong user") + } + role := claims["role"].(string) + + token, err := s.repository.FindByRefreshToken(sub, refreshToken) + if err != nil { + return nil, errors.New("wrong token") + } + + expiredAt := time.Now().Add(time.Hour * 24 * 30) + accessToken, err := s.repository.Generate(sub, expiredAt.Unix(), role) + if err != nil { + return nil, err + } + + refreshExpiredAt := time.Now().Add(time.Hour * 24 * 90) + refreshToken, err = s.repository.Generate(sub, refreshExpiredAt.Unix(), role) + if err != nil { + return nil, err + } + + token.Token = accessToken + token.EndingAt = expiredAt + token.RefreshToken = refreshToken + + return s.repository.Update(token) +} diff --git a/internal/services/user.go b/internal/services/user.go new file mode 100644 index 0000000..366b31d --- /dev/null +++ b/internal/services/user.go @@ -0,0 +1,48 @@ +package services + +import ( + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/repositories" +) + +type userService struct { + repository repositories.UserRepository + tokenRepository repositories.TokenRepository +} + +type UserService interface { + List(string, int, int) (*[]models.User, error) + Total(string) (int64, error) + Find(int64) (*models.User, error) + FindByUsername(string) (*models.User, error) + Create(*models.User) (*models.User, error) +} + +func NewUserService(repository repositories.UserRepository, tokenRepository repositories.TokenRepository) UserService { + return &userService{ + repository: repository, + tokenRepository: tokenRepository, + } +} + +func (s *userService) List(q string, page int, limit int) (*[]models.User, error) { + return s.repository.List(q, page, limit) +} + +func (s *userService) Total(q string) (int64, error) { + return s.repository.Total(q) +} + +func (s *userService) Find(id int64) (*models.User, error) { + return s.repository.Find(id) +} + +func (s *userService) FindByUsername(username string) (*models.User, error) { + return s.repository.FindByUsername(username) +} + +func (s *userService) Create(user *models.User) (*models.User, error) { + result, err := s.repository.Create(user) + + return result, err +} diff --git a/internal/services/userquiz.go b/internal/services/userquiz.go new file mode 100644 index 0000000..a6a9533 --- /dev/null +++ b/internal/services/userquiz.go @@ -0,0 +1,94 @@ +package services + +import ( + "fmt" + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/repositories" +) + +type userQuizService struct { + repository repositories.UserQuizRepository + quizRepository repositories.QuizRepository +} + +type UserQuizService interface { + List(int64, int, int) (*[]models.UserQuiz, error) + Total(int64) (int64, error) + + Find(int64) (*models.UserQuiz, error) + Create(*models.UserQuiz) (*models.UserQuiz, error) + Insert([]models.UserQuiz) ([]models.UserQuiz, error) + Update(*models.UserQuiz) (*models.UserQuiz, error) + Delete(int64) error + Generate(int64, *models.UserQuizPaper) ([]models.UserQuiz, error) +} + +func NewUserQuizService(repository repositories.UserQuizRepository, quizRepository repositories.QuizRepository) UserQuizService { + return &userQuizService{ + repository: repository, + quizRepository: quizRepository, + } +} + +func (s *userQuizService) List(user_quiz_paper_id int64, page int, limit int) (*[]models.UserQuiz, error) { + return s.repository.List(user_quiz_paper_id, page, limit) +} + +func (s *userQuizService) Total(user_quiz_paper_id int64) (int64, error) { + return s.repository.Total(user_quiz_paper_id) +} + +func (s *userQuizService) Find(id int64) (*models.UserQuiz, error) { + return s.repository.Find(id) +} + +func (s *userQuizService) Create(quiz *models.UserQuiz) (*models.UserQuiz, error) { + result, err := s.repository.Create(quiz) + + return result, err +} + +func (s *userQuizService) Insert(items []models.UserQuiz) ([]models.UserQuiz, error) { + result, err := s.repository.Insert(items) + + return result, err +} + +func (s *userQuizService) Update(quiz *models.UserQuiz) (*models.UserQuiz, error) { + result, err := s.repository.Update(quiz) + + return result, err +} + +func (s *userQuizService) Delete(id int64) error { + err := s.repository.Delete(id) + + return err +} + +func (s *userQuizService) Generate(quiz_paper_id int64, userQuizPaper *models.UserQuizPaper) ([]models.UserQuiz, error) { + var userQuizzes []models.UserQuiz + quizzes, err := s.quizRepository.List(quiz_paper_id, 1, 1000) + if err != nil { + return nil, err + } + + for _, quiz := range *quizzes { + userQuiz := models.UserQuiz{ + GUID: quiz.GUID, + CenterID: quiz.CenterID, + UserQuizPaperID: userQuizPaper.ID, + UserID: userQuizPaper.UserID, + No: quiz.No, + QuestionType: quiz.QuestionType, + Question: quiz.Question, + Content: quiz.Content, + } + + fmt.Println(userQuiz.Content) + // fmt.Println(userQuiz.Answer) + userQuizzes = append(userQuizzes, userQuiz) + } + + return userQuizzes, nil +} diff --git a/internal/services/userquizpaper.go b/internal/services/userquizpaper.go new file mode 100644 index 0000000..3b87367 --- /dev/null +++ b/internal/services/userquizpaper.go @@ -0,0 +1,83 @@ +package services + +import ( + "learnsteam/cslms-api/internal/models" + "learnsteam/cslms-api/internal/repositories" + + "github.com/google/uuid" +) + +type userQuizPaperService struct { + repository repositories.UserQuizPaperRepository +} + +type UserQuizPaperService interface { + List(string, int, int) (*[]models.UserQuizPaper, error) + Total(string) (int64, error) + + Find(int64) (*models.UserQuizPaper, error) + Create(*models.UserQuizPaper) (*models.UserQuizPaper, error) + Insert([]models.UserQuizPaper) ([]models.UserQuizPaper, error) + Update(*models.UserQuizPaper) (*models.UserQuizPaper, error) + Delete(string) error + Generate([]int64, *models.QuizPaper) []models.UserQuizPaper +} + +func NewUserQuizPaperService(repository repositories.UserQuizPaperRepository) UserQuizPaperService { + return &userQuizPaperService{ + repository: repository, + } +} + +func (s *userQuizPaperService) List(q string, page int, limit int) (*[]models.UserQuizPaper, error) { + return s.repository.List(q, page, limit) +} + +func (s *userQuizPaperService) Total(q string) (int64, error) { + return s.repository.Total(q) +} + +func (s *userQuizPaperService) Find(id int64) (*models.UserQuizPaper, error) { + return s.repository.Find(id) +} + +func (s *userQuizPaperService) Create(userQuizPaper *models.UserQuizPaper) (*models.UserQuizPaper, error) { + result, err := s.repository.Create(userQuizPaper) + + return result, err +} + +func (s *userQuizPaperService) Insert(items []models.UserQuizPaper) ([]models.UserQuizPaper, error) { + result, err := s.repository.Insert(items) + + return result, err +} + +func (s *userQuizPaperService) Update(userQuizPaper *models.UserQuizPaper) (*models.UserQuizPaper, error) { + result, err := s.repository.Update(userQuizPaper) + + return result, err +} + +func (s *userQuizPaperService) Delete(id string) error { + err := s.repository.Delete(id) + + return err +} + +func (s *userQuizPaperService) Generate(users []int64, quizPaper *models.QuizPaper) []models.UserQuizPaper { + var userQuizPapers []models.UserQuizPaper + + for _, user_id := range users { + userQuizPaper := models.UserQuizPaper{ + GUID: uuid.NewString(), + CenterID: quizPaper.CenterID, + UserID: user_id, + QuizPaperID: quizPaper.ID, + } + + userQuizPapers = append(userQuizPapers, userQuizPaper) + } + + return userQuizPapers +} diff --git a/test/program_test.go b/test/program_test.go new file mode 100644 index 0000000..9fe1de6 --- /dev/null +++ b/test/program_test.go @@ -0,0 +1,170 @@ +package learsteam_quiz_test + +// type ProgramTestSuite struct { +// suite.Suite +// db *gorm.DB +// repository repositories.ProgramRepository +// service services.ProgramService +// controller controllers.ProgramController +// } + +// func (suite *ProgramTestSuite) SetupSuite() { +// // err := os.Remove("test.db") +// // if err != nil { +// // suite.Fail("Failed to remove the test database file") +// // } + +// gorm_config := gorm.Config{NamingStrategy: schema.NamingStrategy{SingularTable: true}} +// db, _ := gorm.Open(sqlite.Open("test.db"), &gorm_config) +// db.AutoMigrate(&models.Program{}) + +// repository := repositories.NewProgramRepository(db) +// service := services.NewProgramService(repository) +// controller := controllers.NewProgramController(service) + +// suite.db = db +// suite.service = service +// suite.repository = repository +// suite.controller = controller + +// suite.CreateSampleData() +// } + +// func (suite *ProgramTestSuite) CreateSampleData() { +// var programs []models.Program +// for i := 1; i < 101; i++ { +// t := []string{"tag" + strconv.Itoa(i), "taga", "tagb"} +// var j datatypes.JSON +// j, _ = json.Marshal(t) +// p := models.Program{ +// ID: strconv.Itoa(i) + "_id", +// Subject: strconv.Itoa(i) + " subject", +// Content: strconv.Itoa(i) + " content", +// Tag: j, +// Tags: j.String(), +// Status: "on", +// PublishAt: time.Now(), +// UpdatedAt: time.Now(), +// CreatedAt: time.Now(), +// } +// programs = append(programs, p) +// } + +// for _, program := range programs { +// suite.db.Create(&program) +// } +// } + +// func (suite *ProgramTestSuite) TearDownSuite() { +// // DB 삭제 +// suite.db.Migrator().DropTable(&models.Program{}) +// err := os.Remove("test.db") +// if err != nil { +// suite.Fail("Failed to remove the test database file") +// } +// } + +// func (suite *ProgramTestSuite) SetupTest() { + +// } + +// func (suite *ProgramTestSuite) TearDownTest() { + +// } + +// func TestProgramSuite(t *testing.T) { +// suite.Run(t, new(ProgramTestSuite)) +// } + +// // 목록 테스트 : 1page, 10개 +// func (suite *ProgramTestSuite) TestListProgramSuccess() { +// programs, err := suite.repository.List("", "", 1, 10) +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), programs) +// assert.Equal(suite.T(), 10, len(*programs)) +// program0 := (*programs)[0] +// assert.Equal(suite.T(), "100_id", program0.ID) + +// program9 := (*programs)[9] +// assert.Equal(suite.T(), "91_id", program9.ID) +// } + +// // 목록 테스트 : tag 검색 +// func (suite *ProgramTestSuite) TestListProgramTagSearchSuccess() { +// programs, err := suite.repository.List("", "tag9", 1, 10) +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), programs) +// assert.Equal(suite.T(), 10, len(*programs)) +// } + +// // Total 테스트 +// func (suite *ProgramTestSuite) TestTotalSuccess() { +// total, err := suite.repository.Total("", "") +// assert.NoError(suite.T(), err) +// assert.Equal(suite.T(), int64(100), total) +// } + +// // Total 테스트 : tag 검색 +// func (suite *ProgramTestSuite) TestTotalTagSuccess() { +// total, err := suite.repository.Total("", "tag2") +// assert.NoError(suite.T(), err) +// assert.Equal(suite.T(), int64(11), total) +// } + +// // // 수정 테스트 +// // func (suite *ProgramTestSuite) TestUpdateProgramSuccess() { +// // t := []string{"TAG"} +// // var j datatypes.JSON +// // j, _ = json.Marshal(t) +// // p := &models.Program{ +// // ID: uuid.NewString(), +// // Subject: "My Subject", +// // Content: "My Content", +// // Tag: j, +// // Tags: j.String(), +// // Status: "on", +// // PublishAt: time.Now(), +// // UpdatedAt: time.Now(), +// // CreatedAt: time.Now(), +// // } + +// // program, err := suite.repository.Create(p) +// // assert.NoError(suite.T(), err) + +// // program.Subject = "My Subject Updated" +// // program.Content = "My Content Updated" +// // program.Tag = j +// // program.Tags = j.String() +// // program.Status = "draft" + +// // updatedProgram, err := suite.repository.Update(program) +// // assert.NoError(suite.T(), err) +// // assert.Equal(suite.T(), program.Subject, updatedProgram.Subject) +// // assert.Equal(suite.T(), program.Content, updatedProgram.Content) +// // assert.Equal(suite.T(), program.Tag, updatedProgram.Tag) +// // assert.Equal(suite.T(), program.Status, updatedProgram.Status) +// // } + +// // // 삭제 테스트 +// // func (suite *ProgramTestSuite) TestDeleteProgramSuccess() { +// // t := []string{"TAG"} +// // var j datatypes.JSON +// // j, _ = json.Marshal(t) +// // p := &models.Program{ +// // ID: uuid.NewString(), +// // Subject: "My Subject", +// // Content: "My Content", +// // Tag: j, +// // Tags: j.String(), +// // Status: "on", +// // PublishAt: time.Now(), +// // UpdatedAt: time.Now(), +// // CreatedAt: time.Now(), +// // } +// // _, err := suite.repository.Create(p) +// // assert.NoError(suite.T(), err) +// // err = suite.repository.Delete(p.ID) +// // assert.NoError(suite.T(), err) +// // _, err = suite.repository.Find(p.ID) +// // assert.Error(suite.T(), err) +// // } diff --git a/test/quiz_test.go b/test/quiz_test.go new file mode 100644 index 0000000..1244357 --- /dev/null +++ b/test/quiz_test.go @@ -0,0 +1,172 @@ +package learsteam_quiz_test + +import ( + "learnsteam/cslms-api/internal/controllers" + "learnsteam/cslms-api/internal/repositories" + "learnsteam/cslms-api/internal/services" + + "github.com/stretchr/testify/suite" + "gorm.io/gorm" +) + +type QuizTestSuite struct { + suite.Suite + db *gorm.DB + repository repositories.QuizRepository + service services.QuizService + controller controllers.QuizController +} + +// func (suite *QuizTestSuite) SetupSuite() { +// // err := os.Remove("test.db") +// // if err != nil { +// // suite.Fail("Failed to remove the test database file") +// // } + +// gorm_config := gorm.Config{NamingStrategy: schema.NamingStrategy{SingularTable: true}} +// db, _ := gorm.Open(sqlite.Open("test.db"), &gorm_config) +// db.AutoMigrate(&models.Quiz{}) + +// repository := repositories.NewQuizRepository(db) +// service := services.NewQuizService(repository) +// controller := controllers.NewQuizController(service) + +// suite.db = db +// suite.service = service +// suite.repository = repository +// suite.controller = controller + +// suite.CreateSampleData() +// } + +// func (suite *QuizTestSuite) CreateSampleData() { +// var quizzes []models.Quiz +// quiz_paper_id := "1_id" + +// c := []string{"1", "2", "3", "4"} +// var j datatypes.JSON +// j, _ = json.Marshal(c) + +// for i := 1; i < 6; i++ { +// q := models.Quiz{ +// ID: strconv.Itoa(i), +// QuizPaperID: quiz_paper_id, +// No: i, +// QuestionType: "multiple", +// Question: strconv.Itoa(i) + " question", +// Content: j, +// Answer: j, +// Hint: "힌트입니다.", +// Comment: "설명입니다.", +// UpdatedAt: time.Now(), +// CreatedAt: time.Now(), +// } +// quizzes = append(quizzes, q) +// } +// for _, quiz := range quizzes { +// suite.db.Create(&quiz) +// } +// } + +// func (suite *QuizTestSuite) TearDownSuite() { +// // DB 삭제 +// suite.db.Migrator().DropTable(&models.Quiz{}) +// err := os.Remove("test.db") +// if err != nil { +// suite.Fail("Failed to remove the test database file") +// } +// } + +// func (suite *QuizTestSuite) SetupTest() { +// suite.CreateSampleData() +// } + +// func (suite *QuizTestSuite) TearDownTest() { + +// } + +// func TestQuizSuite(t *testing.T) { +// suite.Run(t, new(QuizTestSuite)) +// } + +// // 목록 테스트 +// func (suite *QuizTestSuite) TestListQuizSuccess() { +// quizzes, err := suite.repository.List("id_1", 1, 10) +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), quizzes) +// assert.Equal(suite.T(), 5, len(*quizzes)) +// } + +// // Find 테스트 +// func (suite *QuizTestSuite) TestFindQuizSuccess() { +// quiz, err := suite.repository.Find("1") +// assert.NoError(suite.T(), err) +// assert.Equal(suite.T(), "1", quiz.ID) +// } + +// // Find 테스트 +// func (suite *QuizTestSuite) TestFindQuizFail() { +// _, err := suite.repository.Find("x") +// assert.Error(suite.T(), err) +// } + +// // 수정 테스트 +// func (suite *QuizTestSuite) TestUpdateQuizFail() { +// c := []string{"1", "2", "3", "4"} +// var j datatypes.JSON +// j, _ = json.Marshal(c) +// q := &models.Quiz{ +// ID: uuid.NewString(), +// QuizPaperID: uuid.NewString(), +// No: 1, +// QuestionType: "multiple", +// Question: "question", +// Content: j, +// Answer: j, +// Hint: "힌트입니다.", +// Comment: "설명입니다.", +// UpdatedAt: time.Now(), +// CreatedAt: time.Now(), +// } + +// quiz, err := suite.repository.Create(q) +// assert.NoError(suite.T(), err) + +// quiz.QuestionType = "check" +// quiz.Content = j +// quiz.Answer = j +// quiz.Hint = "힌트입니다!" + +// updatedQuiz, err := suite.repository.Update(quiz) +// assert.NoError(suite.T(), err) + +// assert.Equal(suite.T(), updatedQuiz.QuestionType, quiz.QuestionType) +// assert.Equal(suite.T(), updatedQuiz.Content, quiz.Content) +// assert.Equal(suite.T(), updatedQuiz.Answer, quiz.Answer) +// } + +// // 삭제 테스트 +// func (suite *QuizTestSuite) TestDeleteQuizSuccess() { +// c := []string{"1", "2", "3", "4"} +// var j datatypes.JSON +// j, _ = json.Marshal(c) +// q := &models.Quiz{ +// ID: uuid.NewString(), +// QuizPaperID: uuid.NewString(), +// No: 1, +// QuestionType: "multiple", +// Question: "question", +// Content: j, +// Answer: j, +// Hint: "힌트입니다.", +// Comment: "설명입니다.", +// UpdatedAt: time.Now(), +// CreatedAt: time.Now(), +// } +// _, err := suite.repository.Create(q) +// assert.NoError(suite.T(), err) +// err = suite.repository.Delete(q.ID) +// assert.NoError(suite.T(), err) +// _, err = suite.repository.Find(q.ID) +// assert.Error(suite.T(), err) +// } diff --git a/test/token_test.go b/test/token_test.go new file mode 100644 index 0000000..4857201 --- /dev/null +++ b/test/token_test.go @@ -0,0 +1,245 @@ +package learsteam_quiz_test + +// type TokenTestSuite struct { +// suite.Suite +// db *gorm.DB +// repository repositories.TokenRepository +// service services.TokenService +// controller controllers.TokenController +// } + +// func (suite *TokenTestSuite) SetupSuite() { +// err := os.Remove("test.db") +// if err != nil { +// suite.Fail("Failed to remove the test database file") +// } + +// database.Init() +// gorm_config := gorm.Config{NamingStrategy: schema.NamingStrategy{SingularTable: true}} +// db, _ := gorm.Open(sqlite.Open("test.db"), &gorm_config) +// repository := repositories.NewTokenRepository(db) +// service := services.NewTokenService(repository) +// controller := controllers.NewTokenController(service) + +// suite.db = db +// suite.service = service +// suite.repository = repository +// suite.controller = controller + +// suite.CreateSampleData() +// } + +// func (suite *TokenTestSuite) CreateSampleData() { +// suite.db.AutoMigrate(&models.Token{}) +// var tokens []models.Token +// for i := 1; i < 100; i++ { +// expire_at := time.Now().Add(time.Hour * 24 * time.Duration(i+45)) +// tokenString, _ := suite.service.Generate("superrichquiz_octet", expire_at.Unix()) +// token := models.Token{ +// ID: uuid.NewString(), +// Token: tokenString, +// Status: "valid", +// ExpireAt: expire_at, +// UpdatedAt: time.Now(), +// CreatedAt: time.Now(), +// } +// tokens = append(tokens, token) +// } + +// for _, token := range tokens { +// suite.db.Create(&token) +// } +// } + +// func (suite *TokenTestSuite) TearDownSuite() { +// // suite.db.Migrator().DropTable(&models.Token{}) + +// // err := os.Remove("test.db") +// // if err != nil { +// // suite.Fail("Failed to remove the test database file") +// // } +// } +// func (suite *TokenTestSuite) SetupTest() { +// // suite.userRepository.DeleteByName("testUserName0001") +// } + +// func (suite *TokenTestSuite) TearDownTest() { + +// } + +// func TestTokenSuite(t *testing.T) { +// suite.Run(t, new(TokenTestSuite)) +// } + +// // 토큰 생성 테스트 +// func (suite *TokenTestSuite) TestGenerateTokenSuccess() { +// user_id := "testuser" +// expire_at := time.Now().Add(time.Hour * 24 * 365).Unix() +// token, err := suite.service.Generate(user_id, expire_at) +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), token) +// } + +// // 토큰 생성 테스트 +// func (suite *TokenTestSuite) TestGenerateTokenString() { +// user_id := "superrichquiz_octet" +// expire_at := time.Now().Add(time.Hour * 24 * 365 * 100).Unix() +// tokenString, err := suite.service.Generate(user_id, expire_at) +// assert.NoError(suite.T(), err) + +// jwtToken, err := suite.service.VerifyTokenString(tokenString) +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), jwtToken) +// } + +// // 토큰 검증 테스트 +// func (suite *TokenTestSuite) TestVerifyTokenString() { +// user_id := "testuser" +// expire_at := time.Now().Add(time.Hour * 24 * 365).Unix() +// tokenString, err := suite.service.Generate(user_id, expire_at) +// assert.NoError(suite.T(), err) + +// jwtToken, err := suite.service.VerifyTokenString(tokenString) +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), jwtToken) +// assert.Equal(suite.T(), tokenString, jwtToken.Raw) +// } + +// // 토큰 유효성 테스트 +// func (suite *TokenTestSuite) TestValidToken() { +// user_id := "testuser" +// expire_at := time.Now().Add(time.Hour * 24 * 365).Unix() +// tokenString, err := suite.service.Generate(user_id, expire_at) +// assert.NoError(suite.T(), err) + +// jwtToken, err := suite.service.VerifyTokenString(tokenString) +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), jwtToken) + +// valid, err := suite.service.ValidToken(jwtToken) +// assert.NoError(suite.T(), err) +// assert.True(suite.T(), valid) +// } + +// // 토큰 유효성 테스트 : Expire +// func (suite *TokenTestSuite) TestExpiredToken() { +// user_id := "testuser" +// expire_at := time.Now().Add(time.Hour * 1).Unix() +// tokenString, err := suite.service.Generate(user_id, expire_at) +// assert.NoError(suite.T(), err) + +// jwtToken, err := suite.service.GetJwtToken(tokenString) +// assert.NoError(suite.T(), err) +// // assert.Nil(suite.T(), jwtToken) + +// expirationTime, err := jwtToken.Claims.GetExpirationTime() +// assert.NoError(suite.T(), err) +// assert.Greater(suite.T(), expirationTime.Time, time.Now()) + +// expire_at = time.Now().Add(-time.Hour * 1).Unix() +// tokenString, err = suite.service.Generate(user_id, expire_at) +// assert.NoError(suite.T(), err) + +// jwtToken, err = suite.service.GetJwtToken(tokenString) +// assert.Error(suite.T(), err) +// // assert.Nil(suite.T(), jwtToken) + +// expirationTime, err = jwtToken.Claims.GetExpirationTime() +// assert.NoError(suite.T(), err) +// assert.Greater(suite.T(), time.Now(), expirationTime.Time) +// } + +// func (suite *TokenTestSuite) TestCreateToken() { +// dateString := "2023-08-15T01:50:08.706Z" +// layout := "2006-01-02T15:04:05.000Z" +// tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbklkeCI6MTc2NTQyLCJ0b2tlblR5cGUiOiJXQUxMRVQiLCJ0b2tlbkV4cGlyZWREYXRlIjoiMjAyMy0wOC0xNVQwMTo1MDowOC43MDZaIn0.skgP6ysLNx6KRDBYZy3miZW1Q95iV_Cw0ZVWXj1tJyw" +// expire_at, _ := time.Parse(layout, dateString) +// token := models.Token{ +// ID: uuid.NewString(), +// Token: tokenString, +// Status: "valid", +// ExpireAt: expire_at, +// } +// result, err := suite.repository.Create(&token) + +// assert.NoError(suite.T(), err) +// assert.Equal(suite.T(), tokenString, result.Token) +// } + +// // 리프레시할 토큰 목록 +// func (suite *TokenTestSuite) TestListOfTokensToRefresh() { +// tokens, err := suite.repository.ListRefresh() +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), tokens) +// assert.Greater(suite.T(), len(*tokens), 0) +// } + +// // 토큰 리프레시 +// func (suite *TokenTestSuite) TestOldTokenRefresh() { +// tokens, err := suite.repository.ListRefresh() +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), tokens) + +// for _, token := range *tokens { +// token.Status = "expired" +// result, err := suite.repository.Update(&token) +// assert.NoError(suite.T(), err) +// assert.Equal(suite.T(), "expired", result.Status) + +// user_id := "superrichquiz_octet" +// expire_at := result.ExpireAt.Add(time.Hour * 24 * 90) +// tokenString, err := suite.service.Generate(user_id, expire_at.Unix()) +// assert.NoError(suite.T(), err) + +// refreshedToken := models.Token{ +// ID: uuid.NewString(), +// Token: tokenString, +// Status: "valid", +// ExpireAt: expire_at, +// } + +// newToken, err := suite.repository.Create(&refreshedToken) +// assert.NoError(suite.T(), err) +// assert.Equal(suite.T(), tokenString, newToken.Token) +// } +// } + +// func (suite *TokenTestSuite) TestResponseToken() { +// respBody := []byte(`{ +// "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbklkeCI6MTc1MzU4LCJ0b2tlblR5cGUiOiJXQUxMRVQiLCJ0b2tlbkV4cGlyZWREYXRlIjoiMjAyMy0wOC0xNFQwNToxMTo1Ni41MzhaIn0.e1i4_y8ItC8Vje13Ew8NHwZTElOMBObIZjpGLgRCdyE", +// "tokenBody": { +// "tokenExpiredDate": "2023-08-13T06:06:12.065Z", +// "tokenIdx": 174025, +// "tokenType": "WALLET" +// } +// }`) + +// var response map[string]interface{} +// err := json.Unmarshal(respBody, &response) +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), response) +// assert.Equal(suite.T(), response["token"], "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbklkeCI6MTc1MzU4LCJ0b2tlblR5cGUiOiJXQUxMRVQiLCJ0b2tlbkV4cGlyZWREYXRlIjoiMjAyMy0wOC0xNFQwNToxMTo1Ni41MzhaIn0.e1i4_y8ItC8Vje13Ew8NHwZTElOMBObIZjpGLgRCdyE") + +// tokenBody := response["tokenBody"].(map[string]interface{}) +// tokenExpiredDate := tokenBody["tokenExpiredDate"].(string) +// assert.Equal(suite.T(), "2023-08-13T06:06:12.065Z", tokenExpiredDate) +// } + +// func (suite *TokenTestSuite) TestResponseError() { +// respBody := []byte(`{ +// "errorCode": "ERR_0105001", +// "message": "Invalid token" +// }`) + +// var response map[string]interface{} +// err := json.Unmarshal(respBody, &response) +// assert.NoError(suite.T(), err) +// assert.NotNil(suite.T(), response) + +// assert.NotNil(suite.T(), response["errorCode"]) +// assert.Equal(suite.T(), "ERR_0105001", response["errorCode"].(string)) + +// if response["errorCode"] != nil && response["errorCode"].(string) == "ERR_0105001" { +// assert.True(suite.T(), true) +// } +// }