Skip to content
This repository has been archived by the owner on Dec 4, 2024. It is now read-only.

Commit

Permalink
Merge pull request #32 from swibly/feat/languages
Browse files Browse the repository at this point in the history
devkcud authored Jul 15, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents b91d555 + 8f9847c commit 4eac204
Showing 14 changed files with 379 additions and 106 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ WORKDIR /root/

COPY --from=builder /app/cmd/api/main .
COPY config /root/config
COPY translations /root/translations

EXPOSE 8080

8 changes: 5 additions & 3 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import (
"github.com/devkcud/arkhon-foundation/arkhon-api/internal/service"
"github.com/devkcud/arkhon-foundation/arkhon-api/pkg/db"
"github.com/devkcud/arkhon-foundation/arkhon-api/pkg/middleware"
"github.com/devkcud/arkhon-foundation/arkhon-api/translations"
"github.com/gin-gonic/gin"
)

@@ -20,14 +21,15 @@ func main() {
db.Load()

service.Init()
translations.Init("./translations")

gin.SetMode(config.Router.GinMode)

router := gin.New()
router.Use(gin.Logger(), gin.Recovery(), middleware.GetAPIKey)
router.Use(gin.Logger(), gin.Recovery(), middleware.DetectLanguage, middleware.GetAPIKey)

router.GET("/healthz", func(ctx *gin.Context) {
ctx.Writer.WriteString("Hello, world!")
router.GET("/healthcare", func(ctx *gin.Context) {
ctx.Writer.WriteString(ctx.Keys["lang"].(translations.Translation).Hello)
})

v1.NewRouter(router)
41 changes: 28 additions & 13 deletions internal/controller/http/v1/apikey.go
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ import (
"github.com/devkcud/arkhon-foundation/arkhon-api/internal/service"
"github.com/devkcud/arkhon-foundation/arkhon-api/pkg/middleware"
"github.com/devkcud/arkhon-foundation/arkhon-api/pkg/utils"
"github.com/devkcud/arkhon-foundation/arkhon-api/translations"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
@@ -26,15 +27,17 @@ func newAPIKeyRoutes(handler *gin.RouterGroup) {

specific := h.Group("/:key")
specific.Use(func(ctx *gin.Context) {
dict := translations.GetTranslation(ctx)

key, err := service.APIKey.Find(ctx.Param("key"))
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
ctx.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": "No API key found."})
ctx.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": dict.NoAPIKeyFound})
return
}

log.Print(err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
return
}

@@ -49,6 +52,8 @@ func newAPIKeyRoutes(handler *gin.RouterGroup) {
}

func GetAllAPIKeys(ctx *gin.Context) {
dict := translations.GetTranslation(ctx)

var (
page int = 1
perpage int = 10
@@ -65,14 +70,16 @@ func GetAllAPIKeys(ctx *gin.Context) {
keys, err := service.APIKey.FindAll(page, perpage)
if err != nil {
log.Print(err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
return
}

ctx.JSON(http.StatusOK, keys)
}

func GetMyAPIKeys(ctx *gin.Context) {
dict := translations.GetTranslation(ctx)

issuer := ctx.Keys["auth_user"].(*dto.ProfileSearch)

var (
@@ -90,14 +97,16 @@ func GetMyAPIKeys(ctx *gin.Context) {

keys, err := service.APIKey.FindByOwnerID(issuer.ID, page, perpage)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
return
}

ctx.JSON(http.StatusOK, keys)
}

func CreateAPIKey(ctx *gin.Context) {
dict := translations.GetTranslation(ctx)

var issuerID uint = 0
if u, exists := ctx.Get("auth_user"); exists {
issuerID = u.(*dto.ProfileSearch).ID
@@ -111,51 +120,57 @@ func CreateAPIKey(ctx *gin.Context) {
newKey, err := service.APIKey.Create(issuerID, uint(maxUsage))
if err != nil {
log.Printf("Error generating new API key: %v", err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Couldn't generate new key"})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
return
}

ctx.JSON(http.StatusCreated, newKey)
}

func GetAPIKeyInfo(ctx *gin.Context) {
dict := translations.GetTranslation(ctx)

key, err := service.APIKey.Find(ctx.Param("key"))
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
ctx.JSON(http.StatusNotFound, gin.H{"error": "No API key found."})
ctx.JSON(http.StatusNotFound, gin.H{"error": dict.NoAPIKeyFound})
return
}

log.Print(err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
return
}

ctx.JSON(http.StatusOK, key)
}

func DestroyAPIKey(ctx *gin.Context) {
dict := translations.GetTranslation(ctx)

if err := service.APIKey.Delete(ctx.Keys["api_key_lookup"].(*model.APIKey).Key); err != nil {
log.Printf("Error destroying API key: %v", err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Couldn't destroy key"})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
return
}

ctx.JSON(http.StatusOK, gin.H{"message": "Destroyied key"})
ctx.JSON(http.StatusOK, gin.H{"message": gin.H{"error": dict.APIKeyDestroyed}})
}

func UpdateAPIKey(ctx *gin.Context) {
dict := translations.GetTranslation(ctx)

key := ctx.Keys["api_key_lookup"].(*model.APIKey)

var body dto.APIKey
if err := ctx.BindJSON(&body); err != nil {
log.Print(err)
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Bad body format"})
ctx.JSON(http.StatusBadRequest, gin.H{"error": dict.InvalidBody})
return
}

if errs := utils.ValidateStruct(&body); errs != nil {
err := utils.ValidateErrorMessage(errs[0])
err := utils.ValidateErrorMessage(ctx, errs[0])

log.Print(err)
ctx.JSON(http.StatusBadRequest, gin.H{"error": gin.H{err.Param: err.Message}})
@@ -164,9 +179,9 @@ func UpdateAPIKey(ctx *gin.Context) {

if err := service.APIKey.Update(key.Key, &body); err != nil {
log.Print(err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
return
}

ctx.JSON(http.StatusOK, gin.H{"message": "API key updated"})
ctx.JSON(http.StatusOK, gin.H{"message": dict.APIKeyUpdated})
}
62 changes: 33 additions & 29 deletions internal/controller/http/v1/auth.go
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import (
"github.com/devkcud/arkhon-foundation/arkhon-api/internal/service"
"github.com/devkcud/arkhon-foundation/arkhon-api/pkg/middleware"
"github.com/devkcud/arkhon-foundation/arkhon-api/pkg/utils"
"github.com/devkcud/arkhon-foundation/arkhon-api/translations"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgconn"
"golang.org/x/crypto/bcrypt"
@@ -28,30 +29,32 @@ func newAuthRoutes(handler *gin.RouterGroup) {
}

func RegisterHandler(ctx *gin.Context) {
dict := translations.GetTranslation(ctx)

var body dto.UserRegister

if err := ctx.BindJSON(&body); err != nil {
log.Print(err)
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Bad body format"})
ctx.JSON(http.StatusBadRequest, gin.H{"error": dict.InvalidBody})
return
}

if errs := utils.ValidateStruct(&body); errs != nil {
err := utils.ValidateErrorMessage(errs[0])
err := utils.ValidateErrorMessage(ctx, errs[0])

log.Print(err)
ctx.JSON(http.StatusBadRequest, gin.H{"error": gin.H{err.Param: err.Message}})
return
}

user, err := service.User.CreateUser(body.FirstName, body.LastName, body.Username, body.Email, body.Password)
user, err := service.User.CreateUser(ctx, body.FirstName, body.LastName, body.Username, body.Email, body.Password)

if err == nil {
if token, err := utils.GenerateJWT(user.ID); err != nil {
log.Print(err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
} else {
ctx.JSON(http.StatusOK, gin.H{"message": "User created", "token": token})
ctx.JSON(http.StatusOK, gin.H{"token": token})
}

return
@@ -67,24 +70,26 @@ func RegisterHandler(ctx *gin.Context) {
var pgErr *pgconn.PgError
// 23505 => duplicated key value violates unique constraint
if errors.Is(err, gorm.ErrDuplicatedKey) || (errors.As(err, &pgErr) && pgErr.Code == "23505") {
ctx.JSON(http.StatusUnauthorized, gin.H{"error": "An user with that username or email already exists."})
ctx.JSON(http.StatusUnauthorized, gin.H{"error": dict.AuthDuplicatedUser})
return
}

ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
}

func LoginHandler(ctx *gin.Context) {
dict := translations.GetTranslation(ctx)

var body dto.UserLogin

if err := ctx.BindJSON(&body); err != nil {
log.Print(err)
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Bad body format"})
ctx.JSON(http.StatusBadRequest, gin.H{"error": dict.InvalidBody})
return
}

if errs := utils.ValidateStruct(&body); errs != nil {
err := utils.ValidateErrorMessage(errs[0])
err := utils.ValidateErrorMessage(ctx, errs[0])

log.Print(err)
ctx.JSON(http.StatusBadRequest, gin.H{"error": gin.H{err.Param: err.Message}})
@@ -97,48 +102,50 @@ func LoginHandler(ctx *gin.Context) {
log.Print(err)

if errors.Is(err, gorm.ErrRecordNotFound) {
ctx.JSON(http.StatusUnauthorized, gin.H{"error": "No user found with that username or email"})
ctx.JSON(http.StatusUnauthorized, gin.H{"error": dict.AuthWrongCredentials})
return
}

ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
return
}

if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(body.Password)); err != nil {
log.Print(err)

if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
ctx.JSON(http.StatusUnauthorized, gin.H{"error": "Password mismatch"})
ctx.JSON(http.StatusUnauthorized, gin.H{"error": dict.AuthWrongCredentials})
return
}

ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
return
}

if token, err := utils.GenerateJWT(user.ID); err != nil {
log.Print(err)

ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
} else {
ctx.JSON(http.StatusOK, gin.H{"message": "User logged in", "token": token})
ctx.JSON(http.StatusOK, gin.H{"token": token})
}
}

func UpdateUserHandler(ctx *gin.Context) {
dict := translations.GetTranslation(ctx)

issuer := ctx.Keys["auth_user"].(*dto.ProfileSearch)

var body dto.UserUpdate

if err := ctx.BindJSON(&body); err != nil {
log.Print(err)
ctx.JSON(http.StatusBadRequest, gin.H{"error": "Bad body format"})
ctx.JSON(http.StatusBadRequest, gin.H{"error": dict.InvalidBody})
return
}

if errs := utils.ValidateStruct(&body); errs != nil {
err := utils.ValidateErrorMessage(errs[0])
err := utils.ValidateErrorMessage(ctx, errs[0])

log.Print(err)
ctx.JSON(http.StatusBadRequest, gin.H{"error": gin.H{err.Param: err.Message}})
@@ -147,48 +154,45 @@ func UpdateUserHandler(ctx *gin.Context) {

if body.Username != "" {
if profile, err := service.User.GetByUsername(body.Username); profile != nil && err == nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "An user with that username already exists"})
ctx.JSON(http.StatusBadRequest, gin.H{"error": dict.AuthDuplicatedUser})
return
}
}

if body.Email != "" {
if profile, err := service.User.GetByEmail(body.Email); profile != nil && err == nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "An user with that email already exists"})
ctx.JSON(http.StatusBadRequest, gin.H{"error": dict.AuthDuplicatedUser})
return
}
}

if body.Password != "" {
if hashedPassword, err := bcrypt.GenerateFromPassword([]byte(body.Password), config.Security.BcryptCost); err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
} else {
body.Password = string(hashedPassword)
}
}

if err := service.User.Update(issuer.ID, &body); err != nil {
log.Print(err)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
return
}

ctx.JSON(http.StatusOK, gin.H{"message": "User updated"})
ctx.JSON(http.StatusOK, gin.H{"message": dict.AuthUserUpdated})
}

func DeleteUserHandler(ctx *gin.Context) {
dict := translations.GetTranslation(ctx)

issuer := ctx.Keys["auth_user"].(*dto.ProfileSearch)

if err := service.User.DeleteUser(issuer.ID); err != nil {
log.Print(err)
if errors.Is(err, gorm.ErrRecordNotFound) {
ctx.JSON(http.StatusBadRequest, gin.H{"error": "User not found."})
return
}

ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error. Please, try again later."})
ctx.JSON(http.StatusInternalServerError, gin.H{"error": dict.InternalServerError})
return
}

ctx.JSON(http.StatusOK, gin.H{"message": "User deleted"})
ctx.JSON(http.StatusOK, gin.H{"message": dict.AuthUserDeleted})
}
Loading

0 comments on commit 4eac204

Please sign in to comment.