first commit
This commit is contained in:
		
							
								
								
									
										158
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,158 @@
 | 
			
		||||
FUNCTION_NAME	=superrichquiz-octet
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
							
								
								
									
										32
									
								
								cmd/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								cmd/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	configs "studioj/boilerplate_go/configs"
 | 
			
		||||
	"studioj/boilerplate_go/internal/database"
 | 
			
		||||
	"studioj/boilerplate_go/internal/helpers"
 | 
			
		||||
	"studioj/boilerplate_go/internal/routers"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/gateway"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								configs/common.dev
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								configs/common.dev
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
const PORT = ":3030"
 | 
			
		||||
const DATABASE_URL = "root:omHO7EEzHm52s9DlZD70P6KPKm2TbODC@tcp(db:3306)/boilerplate?charset=utf8&parseTime=True&loc=Local"
 | 
			
		||||
const SECRET_KEY = "5a14e06d-55a3-418c-9f3f-8fde328d6c49"
 | 
			
		||||
							
								
								
									
										7
									
								
								configs/common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								configs/common.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
const PORT = ":3030"
 | 
			
		||||
const DATABASE_URL = "root:sswha123@tcp(localhost:3306)/boilerplate?charset=utf8&parseTime=True&loc=Local"
 | 
			
		||||
 | 
			
		||||
// const DATABASE_URL = "sqlite.db"
 | 
			
		||||
const SECRET_KEY = "5a14e06d-55a3-418c-9f3f-8fde328d6c49"
 | 
			
		||||
							
								
								
									
										6
									
								
								configs/common.local
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								configs/common.local
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
const PORT = ":3030"
 | 
			
		||||
const DATABASE_URL = "root:sswha123@tcp(localhost:3306)/boilerplate?charset=utf8&parseTime=True&loc=Local"
 | 
			
		||||
//const DATABASE_URL = "sqlite.db"
 | 
			
		||||
const SECRET_KEY = "5a14e06d-55a3-418c-9f3f-8fde328d6c49"
 | 
			
		||||
							
								
								
									
										5
									
								
								configs/common.prod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								configs/common.prod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
const PORT = ":3030"
 | 
			
		||||
const DATABASE_URL = "root:omHO7EEzHm52s9DlZD70P6KPKm2TbODC@tcp(localhost:3306)/boilerplate?charset=utf8&parseTime=True&loc=Local"
 | 
			
		||||
const SECRET_KEY = "5a14e06d-55a3-418c-9f3f-8fde328d6c49"
 | 
			
		||||
							
								
								
									
										18
									
								
								docker/dev/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								docker/dev/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
# 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 .
 | 
			
		||||
 | 
			
		||||
CMD ["sh", "-c", "./bootstrap"]
 | 
			
		||||
							
								
								
									
										29
									
								
								docker/dev/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								docker/dev/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
version: "3"
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  db:
 | 
			
		||||
    image: mysql:latest
 | 
			
		||||
    environment:
 | 
			
		||||
      MYSQL_ROOT_PASSWORD: omHO7EEzHm52s9DlZD70P6KPKm2TbODC
 | 
			
		||||
      MYSQL_DATABASE: boilerplate
 | 
			
		||||
    ports:
 | 
			
		||||
      - "3306:3306"
 | 
			
		||||
    volumes:
 | 
			
		||||
      - db-data:/var/lib/mysql
 | 
			
		||||
  app:
 | 
			
		||||
    build:
 | 
			
		||||
      context: ../../
 | 
			
		||||
      dockerfile: docker/dev/Dockerfile
 | 
			
		||||
    image: studioj/boilerplate_app:dev
 | 
			
		||||
    ports:
 | 
			
		||||
      - "3030:3030"
 | 
			
		||||
    environment:
 | 
			
		||||
      DB_HOST: db
 | 
			
		||||
      DB_PORT: 3306
 | 
			
		||||
      DB_USER: root
 | 
			
		||||
      DB_PASSWORD: omHO7EEzHm52s9DlZD70P6KPKm2TbODC
 | 
			
		||||
      DB_NAME: boilerplate
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - db
 | 
			
		||||
volumes:
 | 
			
		||||
  db-data:
 | 
			
		||||
							
								
								
									
										18
									
								
								docker/local/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								docker/local/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
# 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.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 .
 | 
			
		||||
 | 
			
		||||
CMD ["sh", "-c", "./bootstrap"]
 | 
			
		||||
							
								
								
									
										30
									
								
								docker/local/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								docker/local/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
version: "3"
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  db:
 | 
			
		||||
    image: mysql:latest
 | 
			
		||||
    environment:
 | 
			
		||||
      MYSQL_ROOT_PASSWORD: omHO7EEzHm52s9DlZD70P6KPKm2TbODC
 | 
			
		||||
      MYSQL_DATABASE: boilerplate
 | 
			
		||||
    ports:
 | 
			
		||||
      - "3306:3306"
 | 
			
		||||
    volumes:
 | 
			
		||||
      - db-data:/var/lib/mysql
 | 
			
		||||
  app:
 | 
			
		||||
    build:
 | 
			
		||||
      context: ../../
 | 
			
		||||
      dockerfile: docker/local/Dockerfile
 | 
			
		||||
    image: studioj/boilerplate_app:local
 | 
			
		||||
    ports:
 | 
			
		||||
      - "3030:3030"
 | 
			
		||||
    environment:
 | 
			
		||||
      DB_HOST: db
 | 
			
		||||
      DB_PORT: 3306
 | 
			
		||||
      DB_USER: root
 | 
			
		||||
      DB_PASSWORD: omHO7EEzHm52s9DlZD70P6KPKm2TbODC
 | 
			
		||||
      DB_NAME: boilerplate
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - db
 | 
			
		||||
 | 
			
		||||
volumes:
 | 
			
		||||
  db-data:
 | 
			
		||||
							
								
								
									
										18
									
								
								docker/prod/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								docker/prod/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
# 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 .
 | 
			
		||||
 | 
			
		||||
CMD ["sh", "-c", "./bootstrap"]
 | 
			
		||||
							
								
								
									
										30
									
								
								docker/prod/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								docker/prod/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
version: "3"
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  db:
 | 
			
		||||
    image: mysql:latest
 | 
			
		||||
    environment:
 | 
			
		||||
      MYSQL_ROOT_PASSWORD: omHO7EEzHm52s9DlZD70P6KPKm2TbODC
 | 
			
		||||
      MYSQL_DATABASE: boilerplate
 | 
			
		||||
    ports:
 | 
			
		||||
      - "3306:3306"
 | 
			
		||||
    volumes:
 | 
			
		||||
      - db-data:/var/lib/mysql
 | 
			
		||||
  app:
 | 
			
		||||
    build:
 | 
			
		||||
      context: ../../
 | 
			
		||||
      dockerfile: docker/prod/Dockerfile
 | 
			
		||||
    image: studioj/boilerplate_app
 | 
			
		||||
    ports:
 | 
			
		||||
      - "3030:3030"
 | 
			
		||||
    environment:
 | 
			
		||||
      DB_HOST: db
 | 
			
		||||
      DB_PORT: 3306
 | 
			
		||||
      DB_USER: root
 | 
			
		||||
      DB_PASSWORD: omHO7EEzHm52s9DlZD70P6KPKm2TbODC
 | 
			
		||||
      DB_NAME: boilerplate
 | 
			
		||||
    depends_on:
 | 
			
		||||
      - db
 | 
			
		||||
 | 
			
		||||
volumes:
 | 
			
		||||
  db-data:
 | 
			
		||||
							
								
								
									
										46
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
module studioj/boilerplate_go
 | 
			
		||||
 | 
			
		||||
go 1.20
 | 
			
		||||
 | 
			
		||||
require github.com/gin-gonic/gin v1.9.1
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/aws/aws-lambda-go v1.17.0 // indirect
 | 
			
		||||
	github.com/go-sql-driver/mysql v1.7.0 // indirect
 | 
			
		||||
	github.com/jinzhu/inflection v1.0.0 // indirect
 | 
			
		||||
	github.com/jinzhu/now v1.1.5 // indirect
 | 
			
		||||
	github.com/pkg/errors v0.9.1 // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/apex/gateway v1.1.2
 | 
			
		||||
	github.com/bytedance/sonic v1.9.1 // indirect
 | 
			
		||||
	github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
 | 
			
		||||
	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
 | 
			
		||||
	github.com/gin-contrib/sse v0.1.0 // 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.14.0 // indirect
 | 
			
		||||
	github.com/goccy/go-json v0.10.2 // indirect
 | 
			
		||||
	github.com/golang-jwt/jwt/v5 v5.0.0
 | 
			
		||||
	github.com/google/uuid v1.3.1
 | 
			
		||||
	github.com/json-iterator/go v1.1.12 // indirect
 | 
			
		||||
	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
 | 
			
		||||
	github.com/leodido/go-urn v1.2.4 // indirect
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.19 // 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.0.8 // indirect
 | 
			
		||||
	github.com/stretchr/testify v1.8.4 // indirect
 | 
			
		||||
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 | 
			
		||||
	github.com/ugorji/go/codec v1.2.11 // indirect
 | 
			
		||||
	golang.org/x/arch v0.3.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.9.0
 | 
			
		||||
	golang.org/x/net v0.10.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.8.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.9.0 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.30.0 // indirect
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
			
		||||
	gorm.io/driver/mysql v1.5.2
 | 
			
		||||
	gorm.io/gorm v1.25.5
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										118
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
			
		||||
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.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
 | 
			
		||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
 | 
			
		||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
 | 
			
		||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
 | 
			
		||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
 | 
			
		||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 | 
			
		||||
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.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
 | 
			
		||||
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.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
 | 
			
		||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
 | 
			
		||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 | 
			
		||||
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.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.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
 | 
			
		||||
github.com/go-playground/validator/v10 v10.14.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.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 | 
			
		||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 | 
			
		||||
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
 | 
			
		||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 | 
			
		||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 | 
			
		||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 | 
			
		||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 | 
			
		||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
 | 
			
		||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
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/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.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
 | 
			
		||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
 | 
			
		||||
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/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 | 
			
		||||
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/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
 | 
			
		||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
 | 
			
		||||
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/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.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 | 
			
		||||
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/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/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=
 | 
			
		||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 | 
			
		||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
 | 
			
		||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 | 
			
		||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
 | 
			
		||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
 | 
			
		||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
 | 
			
		||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
 | 
			
		||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
 | 
			
		||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 | 
			
		||||
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.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
 | 
			
		||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
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/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=
 | 
			
		||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 | 
			
		||||
							
								
								
									
										67
									
								
								internal/controllers/auth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								internal/controllers/auth.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
package controllers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"studioj/boilerplate_go/internal/models"
 | 
			
		||||
	"studioj/boilerplate_go/internal/services"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AuthController interface {
 | 
			
		||||
	Register(*gin.Context)
 | 
			
		||||
	Login(*gin.Context)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type authController struct {
 | 
			
		||||
	service services.AuthService
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAuthController(service services.AuthService) AuthController {
 | 
			
		||||
	return &authController{
 | 
			
		||||
		service: service,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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.service.CreateToken(user.ID)
 | 
			
		||||
	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})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (controller *authController) Login(c *gin.Context) {
 | 
			
		||||
	var params models.LoginRequest
 | 
			
		||||
	if c.BindJSON(¶ms) != nil {
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user, err := controller.service.Login(¶ms)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	token, err := controller.service.CreateToken(user.ID)
 | 
			
		||||
	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})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								internal/controllers/token.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								internal/controllers/token.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
package controllers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"studioj/boilerplate_go/internal/services"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TokenController interface {
 | 
			
		||||
	Find(*gin.Context)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type tokenController struct {
 | 
			
		||||
	service services.TokenService
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewTokenController(service services.TokenService) TokenController {
 | 
			
		||||
	return &tokenController{
 | 
			
		||||
		service: service,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (controller *tokenController) Find(c *gin.Context) {
 | 
			
		||||
	id := c.Param("id")
 | 
			
		||||
	user_id := c.GetString("user_id")
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (controller *tokenController) List(c *gin.Context) {
 | 
			
		||||
	id := c.Param("id")
 | 
			
		||||
	user_id := c.GetString("user_id")
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								internal/controllers/user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								internal/controllers/user.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
package controllers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"studioj/boilerplate_go/internal/services"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type UserController interface {
 | 
			
		||||
	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,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (controller *userController) Find(c *gin.Context) {
 | 
			
		||||
	id := c.Param("id")
 | 
			
		||||
	user_id := c.GetString("sub")
 | 
			
		||||
	fmt.Println("id", id)
 | 
			
		||||
	fmt.Println("user_id", user_id)
 | 
			
		||||
 | 
			
		||||
	if user_id != id {
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": "Wrong user"})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result, err := controller.service.FindByID(id)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.JSON(http.StatusOK, result)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								internal/database/database.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								internal/database/database.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
package database
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	config "studioj/boilerplate_go/configs"
 | 
			
		||||
	"studioj/boilerplate_go/internal/models"
 | 
			
		||||
 | 
			
		||||
	"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() {
 | 
			
		||||
	DB.AutoMigrate(
 | 
			
		||||
		&models.User{},
 | 
			
		||||
		&models.Token{},
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								internal/helpers/helpers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								internal/helpers/helpers.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
package helpers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func InLambda() bool {
 | 
			
		||||
	if lambdaTaskRoot := os.Getenv("LAMBDA_TASK_ROOT"); lambdaTaskRoot != "" {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RandomPin() string {
 | 
			
		||||
	min := 100000
 | 
			
		||||
	max := 999999
 | 
			
		||||
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
 | 
			
		||||
	pin := r.Intn(max-min) + min
 | 
			
		||||
	return strconv.Itoa(pin)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Datetime(timeString string) (time.Time, error) {
 | 
			
		||||
	layout := "2006-01-02T15:04:05.000Z"
 | 
			
		||||
	result, err := time.Parse(layout, timeString)
 | 
			
		||||
	return result, err
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								internal/middleware/auth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								internal/middleware/auth.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
package middleware
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	config "studioj/boilerplate_go/configs"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/golang-jwt/jwt/v5"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Auth() 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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fmt.Println("token", extract(c.Request))
 | 
			
		||||
		fmt.Println("sub", *sub)
 | 
			
		||||
 | 
			
		||||
		c.Set("token", extract(c.Request))
 | 
			
		||||
		c.Set("sub", *sub)
 | 
			
		||||
		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) (*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")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sub := claims["sub"].(string)
 | 
			
		||||
 | 
			
		||||
	return &sub, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								internal/middleware/transaction.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								internal/middleware/transaction.go
									
									
									
									
									
										Normal file
									
								
							@@ -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()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								internal/models/auth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								internal/models/auth.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type LoginRequest struct {
 | 
			
		||||
	Username string `json:"username"`
 | 
			
		||||
	Password string `json:"password"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RegisterRequest struct {
 | 
			
		||||
	Username string `json:"username"`
 | 
			
		||||
	Password string `json:"password"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								internal/models/token.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								internal/models/token.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type Token struct {
 | 
			
		||||
	ID           string    `json:"id" db:"id" gorm:"column:id;size:255;primary_key;"`
 | 
			
		||||
	UserID       string    `json:"user_id" db:"user_id" gorm:"column:user_id;size:255;index;"`
 | 
			
		||||
	Token        string    `json:"token" gorm:"column:token;size:255;index;"`
 | 
			
		||||
	RefreshToken string    `json:"refresh_token" gorm:"column:token;size:255;index;"`
 | 
			
		||||
	Status       string    `json:"status" gorm:"column:status;size:10;index;"`
 | 
			
		||||
	ExpireAt     time.Time `json:"expire_at" gorm:"column:expire_at;index;"`
 | 
			
		||||
	UpdatedAt    time.Time `json:"updated_at" gorm:"column:updated_at;index;"`
 | 
			
		||||
	CreatedAt    time.Time `json:"created_at" gorm:"column:created_at;index;"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								internal/models/user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								internal/models/user.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type User struct {
 | 
			
		||||
	ID       string `json:"id" db:"id" gorm:"column:id;size:255;primary_key;"`
 | 
			
		||||
	Username string `json:"username" db:"username" gorm:"column:username;size:50;uniqueIndex;"`
 | 
			
		||||
	Score    int32  `json:"score" db:"score" gorm:"column:score;"`
 | 
			
		||||
	Money    int32  `json:"money" db:"money" gorm:"column:money;"`
 | 
			
		||||
	Password string `json:"-" db:"password" gorm:"column:password;size:255;not null;"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								internal/repositories/token.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								internal/repositories/token.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
package repositories
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	config "studioj/boilerplate_go/configs"
 | 
			
		||||
	"studioj/boilerplate_go/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(string, int64) (string, error)
 | 
			
		||||
	Find(string) (*models.Token, error)
 | 
			
		||||
	Create(*models.Token) (*models.Token, error)
 | 
			
		||||
	Update(*models.Token) (*models.Token, error)
 | 
			
		||||
	Delete(string) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *tokenRepository) Generate(sub string, expire_at int64) (string, error) {
 | 
			
		||||
	claims := jwt.MapClaims{}
 | 
			
		||||
	claims["authorized"] = true
 | 
			
		||||
	claims["sub"] = sub
 | 
			
		||||
	claims["exp"] = expire_at
 | 
			
		||||
	at := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
 | 
			
		||||
	token, err := at.SignedString([]byte(config.SECRET_KEY))
 | 
			
		||||
 | 
			
		||||
	return token, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *tokenRepository) Find(id string) (*models.Token, error) {
 | 
			
		||||
	var token *models.Token
 | 
			
		||||
	err := r.DB.Where("id = ?", id).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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								internal/repositories/user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								internal/repositories/user.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
package repositories
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"studioj/boilerplate_go/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() (*[]models.User, error)
 | 
			
		||||
	FindByID(string) (*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() (*[]models.User, error) {
 | 
			
		||||
	var users *[]models.User
 | 
			
		||||
	err := r.DB.Find(&users).Error
 | 
			
		||||
	return users, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *userRepository) FindByID(id string) (*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("username = ?", 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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								internal/routers/auth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								internal/routers/auth.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
package routers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"studioj/boilerplate_go/internal/controllers"
 | 
			
		||||
	"studioj/boilerplate_go/internal/repositories"
 | 
			
		||||
	"studioj/boilerplate_go/internal/services"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 InitAuthRouter(db *gorm.DB, router *gin.Engine) {
 | 
			
		||||
	r := NewAuthRouter(db, router)
 | 
			
		||||
	r.SetAuthRouter(db, router)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
	return &authRouter{
 | 
			
		||||
		db:              db,
 | 
			
		||||
		userRepository:  userRepository,
 | 
			
		||||
		tokenRepository: tokenRepository,
 | 
			
		||||
		service:         service,
 | 
			
		||||
		tokenService:    tokenService,
 | 
			
		||||
		controller:      controller,
 | 
			
		||||
		router:          router,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *authRouter) SetAuthRouter(db *gorm.DB, router *gin.Engine) {
 | 
			
		||||
	group := router.Group("/auth")
 | 
			
		||||
	group.POST("login", r.controller.Login)
 | 
			
		||||
	group.POST("register", r.controller.Register)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								internal/routers/router.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								internal/routers/router.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
package routers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
 | 
			
		||||
	"studioj/boilerplate_go/internal/database"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var Router *gin.Engine
 | 
			
		||||
 | 
			
		||||
func Init() {
 | 
			
		||||
	gin.SetMode(gin.ReleaseMode)
 | 
			
		||||
	Router = gin.Default()
 | 
			
		||||
	maindb := database.GetDB()
 | 
			
		||||
 | 
			
		||||
	InitAuthRouter(maindb, Router)
 | 
			
		||||
	InitTokenRouter(maindb, Router)
 | 
			
		||||
	InitUserRouter(maindb, Router)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								internal/routers/token.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								internal/routers/token.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
package routers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"studioj/boilerplate_go/internal/controllers"
 | 
			
		||||
	"studioj/boilerplate_go/internal/repositories"
 | 
			
		||||
	"studioj/boilerplate_go/internal/services"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 InitTokenRouter(db *gorm.DB, router *gin.Engine) {
 | 
			
		||||
	r := NewTokenRouter(db, router)
 | 
			
		||||
	r.SetTokenRouter(db, router)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(db *gorm.DB, router *gin.Engine) {
 | 
			
		||||
	// group := router.Group("/token")
 | 
			
		||||
	// group.GET("refresh", middleware.Auth(), r.controller.Refresh)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								internal/routers/user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								internal/routers/user.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
package routers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"studioj/boilerplate_go/internal/controllers"
 | 
			
		||||
	"studioj/boilerplate_go/internal/middleware"
 | 
			
		||||
	"studioj/boilerplate_go/internal/repositories"
 | 
			
		||||
	"studioj/boilerplate_go/internal/services"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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 InitUserRouter(db *gorm.DB, router *gin.Engine) {
 | 
			
		||||
	r := NewUserRouter(db, router)
 | 
			
		||||
	r.SetUserRouter(db, router)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(db *gorm.DB, router *gin.Engine) {
 | 
			
		||||
	group := router.Group("/user")
 | 
			
		||||
	group.GET("/:id", middleware.Auth(), r.controller.Find)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										90
									
								
								internal/services/auth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								internal/services/auth.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
package services
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"studioj/boilerplate_go/internal/models"
 | 
			
		||||
	"studioj/boilerplate_go/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)
 | 
			
		||||
	CreateToken(string) (*models.Token, 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{
 | 
			
		||||
		ID:       uuid.NewString(),
 | 
			
		||||
		Username: request.Username,
 | 
			
		||||
		Password: string(hash),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *authService) CreateToken(user_id string) (*models.Token, error) {
 | 
			
		||||
	tokenExpiredAt := time.Now().Add(time.Hour * 24 * 30)
 | 
			
		||||
	accessToken, err := s.tokenRepository.Generate(user_id, tokenExpiredAt.Unix())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	refreshExpiredAt := time.Now().Add(time.Hour * 24 * 90)
 | 
			
		||||
	refreshToken, err := s.tokenRepository.Generate(user_id, refreshExpiredAt.Unix())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	newToken := &models.Token{
 | 
			
		||||
		ID:           uuid.NewString(),
 | 
			
		||||
		UserID:       user_id,
 | 
			
		||||
		Token:        accessToken,
 | 
			
		||||
		RefreshToken: refreshToken,
 | 
			
		||||
		Status:       "valid",
 | 
			
		||||
		ExpireAt:     tokenExpiredAt,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	token, err := s.tokenRepository.Create(newToken)
 | 
			
		||||
 | 
			
		||||
	return token, err
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										115
									
								
								internal/services/token.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								internal/services/token.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
package services
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	config "studioj/boilerplate_go/configs"
 | 
			
		||||
	"studioj/boilerplate_go/internal/models"
 | 
			
		||||
	"studioj/boilerplate_go/internal/repositories"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang-jwt/jwt/v5"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type tokenService struct {
 | 
			
		||||
	repository repositories.TokenRepository
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TokenService interface {
 | 
			
		||||
	Find(string) (*models.Token, error)
 | 
			
		||||
	Create(*models.Token) (*models.Token, error)
 | 
			
		||||
	Update(*models.Token) (*models.Token, error)
 | 
			
		||||
	Delete(string) error
 | 
			
		||||
 | 
			
		||||
	Generate(string, int64) (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 string) (*models.Token, error) {
 | 
			
		||||
	return s.repository.Find(id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *tokenService) Create(token *models.Token) (*models.Token, error) {
 | 
			
		||||
	return s.repository.Create(token)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) (string, error) {
 | 
			
		||||
	claims := jwt.MapClaims{}
 | 
			
		||||
	claims["authorized"] = true
 | 
			
		||||
	claims["sub"] = user_id
 | 
			
		||||
	claims["exp"] = expire_at
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								internal/services/user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								internal/services/user.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
package services
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"studioj/boilerplate_go/internal/models"
 | 
			
		||||
	"studioj/boilerplate_go/internal/repositories"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type userService struct {
 | 
			
		||||
	repository      repositories.UserRepository
 | 
			
		||||
	tokenRepository repositories.TokenRepository
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type UserService interface {
 | 
			
		||||
	FindByID(string) (*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) FindByID(id string) (*models.User, error) {
 | 
			
		||||
	return s.repository.FindByID(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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								tests/test.db
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								tests/test.db
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										245
									
								
								tests/token_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								tests/token_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,245 @@
 | 
			
		||||
package octet_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)
 | 
			
		||||
// 	}
 | 
			
		||||
// }
 | 
			
		||||
		Reference in New Issue
	
	Block a user