Skip to content

Commit

Permalink
Merge branch 'fix-user-api'
Browse files Browse the repository at this point in the history
  • Loading branch information
masashiy22 committed May 9, 2024
2 parents 2bf8451 + 054d065 commit f1ab854
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 41 deletions.
20 changes: 20 additions & 0 deletions controller/common.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package controller

import (
"context"
"errors"
"fmt"
"os"
"strings"

"github.com/gin-gonic/gin"
"github.com/openkrafter/anytore-backend/logger"
"github.com/openkrafter/anytore-backend/service"
)

Expand All @@ -17,6 +22,21 @@ func ValidateTokenAndGetUserId(c *gin.Context) (int, error) {
return userId, nil
}

func CheckAdminAuthorization(ctx context.Context, userId int) error {
user, err := service.GetUserById(ctx, userId)
if err != nil {
logger.Logger.Error("Failed to get user.", logger.ErrAttr(err))
return err
}

if user.Name != os.Getenv("ADMIN_NAME") {
errMsg := fmt.Sprintf("User %d (%s) is not admin.", userId, user.Name)
logger.Logger.Error(errMsg, logger.ErrAttr(err))
return errors.New(errMsg)
}
return nil
}

func getTokenFromAuthorizationHeader(c *gin.Context) string {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
Expand Down
57 changes: 21 additions & 36 deletions controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,23 @@ func SampleTraningItem(c *gin.Context) {
}

func RegisterRoutes(r *gin.Engine) {
r.GET("/sample", SampleTraningItem) // for debug

r.POST("/login", Login)

r.GET("/sample", SampleTraningItem) // for debug
r.GET("/admin/users", ListUsers)
r.GET("/admin/users/:user-id", GetUser)
r.POST("/admin/users", CreateUser)
r.PUT("/admin/users/:user-id", UpdateUser)
r.DELETE("/admin/users/:user-id", DeleteUser)

r.GET("/training-items", ListTraningItem)
r.GET("/training-items/:training-item-id", GetTraningItem)
r.POST("/training-items", CreateTraningItem)
r.PUT("/training-items/:training-item-id", UpdateTraningItem)
r.DELETE("/training-items/:training-item-id", DeleteTraningItem)
}

func RegisterAdminRoutes(r *gin.Engine) {
adminGroup := r.Group("/admin")
username := os.Getenv("ADMIN_USERNAME")
password := os.Getenv("ADMIN_PASSWORD")
adminGroup.Use(gin.BasicAuth(gin.Accounts{
username: password,
}))

adminGroup.GET("/users", ListUsers)
adminGroup.GET("/users/:user-id", GetUser)
adminGroup.POST("/users", CreateUser)
adminGroup.PUT("/users/:user-id", UpdateUser)
adminGroup.DELETE("/users/:user-id", DeleteUser)
}

func Run() {
logger.Logger.Info("Controller thread start.")

Expand All @@ -57,7 +49,6 @@ func Run() {
setCors(r)
setCSP(r)
RegisterRoutes(r)
RegisterAdminRoutes(r)

// listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
if err := r.Run(); err != nil {
Expand All @@ -67,27 +58,21 @@ func Run() {
}

func setCors(r *gin.Engine) {
if os.Getenv("GIN_MODE") == "release" {
r.Use(cors.New(cors.Config{
AllowOrigins: []string{os.Getenv("PROD_CORS_ORIGIN")},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "Accept"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 6 * time.Hour,
}))
config := cors.DefaultConfig()
if os.Getenv("CORS_ORIGIN") == "*" {
logger.Logger.Debug("CORS setting: allow all origins")
config.AllowAllOrigins = true
} else {
logger.Logger.Debug("CORS setting: debug mode")

r.Use(cors.New(cors.Config{
AllowOrigins: []string{os.Getenv("DEV_CORS_ORIGIN")},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "Accept"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 6 * time.Hour,
}))
logger.Logger.Debug("CORS setting: allow specific origin")
config.AllowOrigins = []string{os.Getenv("CORS_ORIGIN")}
}
config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}
config.AllowHeaders = []string{"Origin", "Content-Type", "Authorization", "Accept"}
config.ExposeHeaders = []string{"Content-Length"}
config.AllowCredentials = true
config.MaxAge = 6 * time.Hour

r.Use(cors.New(config))
}

func setCSP(r *gin.Engine) {
Expand Down
80 changes: 75 additions & 5 deletions controller/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,22 @@ import (
)

func ListUsers(c *gin.Context) {
logger.Logger.Debug("ListUsers called.")
tokenUserId, err := ValidateTokenAndGetUserId(c)
if err != nil {
logger.Logger.Error("Token Error.", logger.ErrAttr(err))
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authenticated"})
return
}

ctx := c.Request.Context()
err = CheckAdminAuthorization(ctx, tokenUserId)
if err != nil {
logger.Logger.Error("ListUsers Failed.", logger.ErrAttr(err))
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized"})
return
}

users, err := service.GetUsers(ctx)
if err != nil {
logger.Logger.Error("ListUsers Failed.", logger.ErrAttr(err))
Expand All @@ -28,14 +43,28 @@ func ListUsers(c *gin.Context) {
}

func GetUser(c *gin.Context) {
tokenUserId, err := ValidateTokenAndGetUserId(c)
if err != nil {
logger.Logger.Error("Token Error.", logger.ErrAttr(err))
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authenticated"})
return
}

ctx := c.Request.Context()
err = CheckAdminAuthorization(ctx, tokenUserId)
if err != nil {
logger.Logger.Error("ListUsers Failed.", logger.ErrAttr(err))
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized"})
return
}

userIdString := c.Param("user-id")
userId, err := strconv.Atoi(userIdString)
if err != nil {
logger.Logger.Error("GetUser Failed. Failed to convert userId string to int.", logger.ErrAttr(err))
return
}

ctx := c.Request.Context()
user, err := service.GetUserById(ctx, userId)
if err != nil {
logger.Logger.Error("GetUser Failed.", logger.ErrAttr(err))
Expand All @@ -51,13 +80,27 @@ func GetUser(c *gin.Context) {
}

func CreateUser(c *gin.Context) {
tokenUserId, err := ValidateTokenAndGetUserId(c)
if err != nil {
logger.Logger.Error("Token Error.", logger.ErrAttr(err))
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authenticated"})
return
}

ctx := c.Request.Context()
err = CheckAdminAuthorization(ctx, tokenUserId)
if err != nil {
logger.Logger.Error("ListUsers Failed.", logger.ErrAttr(err))
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized"})
return
}

var requestBody model.User
if err := c.ShouldBindJSON(&requestBody); err != nil {
logger.Logger.Error("CreateUser Failed. Failed to bind request body.", logger.ErrAttr(err))
return
}

ctx := c.Request.Context()
if err := service.CreateUser(ctx, &requestBody); err != nil {
logger.Logger.Error("CreateUser Failed.", logger.ErrAttr(err))
return
Expand All @@ -67,20 +110,33 @@ func CreateUser(c *gin.Context) {
}

func UpdateUser(c *gin.Context) {
tokenUserId, err := ValidateTokenAndGetUserId(c)
if err != nil {
logger.Logger.Error("Token Error.", logger.ErrAttr(err))
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authenticated"})
return
}

ctx := c.Request.Context()
err = CheckAdminAuthorization(ctx, tokenUserId)
if err != nil {
logger.Logger.Error("ListUsers Failed.", logger.ErrAttr(err))
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized"})
return
}

var requestBody model.User
if err := c.ShouldBindJSON(&requestBody); err != nil {
logger.Logger.Error("UpdateUser Failed. Failed to bind request body.", logger.ErrAttr(err))
return
}

var err error
requestBody.Id, err = strconv.Atoi(c.Param("user-id"))
if err != nil {
logger.Logger.Error("UpdateUser Failed. Failed to convert userId string to int.", logger.ErrAttr(err))
return
}

ctx := c.Request.Context()
if err := service.UpdateUser(ctx, &requestBody); err != nil {
logger.Logger.Error("UpdateUser Failed.", logger.ErrAttr(err))
return
Expand All @@ -90,13 +146,27 @@ func UpdateUser(c *gin.Context) {
}

func DeleteUser(c *gin.Context) {
tokenUserId, err := ValidateTokenAndGetUserId(c)
if err != nil {
logger.Logger.Error("Token Error.", logger.ErrAttr(err))
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authenticated"})
return
}

ctx := c.Request.Context()
err = CheckAdminAuthorization(ctx, tokenUserId)
if err != nil {
logger.Logger.Error("ListUsers Failed.", logger.ErrAttr(err))
c.JSON(http.StatusForbidden, gin.H{"error": "You are not authorized"})
return
}

userId, err := strconv.Atoi(c.Param("user-id"))
if err != nil {
logger.Logger.Error("DeleteUser Failed. Failed to convert userId string to int.", logger.ErrAttr(err))
return
}

ctx := c.Request.Context()
if err := service.DeleteUser(ctx, userId); err != nil {
logger.Logger.Error("DeleteUser Failed.", logger.ErrAttr(err))
return
Expand Down
2 changes: 2 additions & 0 deletions platform/local/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ services:
dynamodb-local:
image: amazon/dynamodb-local
restart: always
user: root
command: "-jar DynamoDBLocal.jar -sharedDb -dbPath /home/dynamodblocal/data"
volumes:
- dynamodb-local-data:/home/dynamodblocal/data
ports:
Expand Down

0 comments on commit f1ab854

Please sign in to comment.