Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@
.vscode
.DS_Store
.idea
.env
.env
/uploads/
/test/
2 changes: 1 addition & 1 deletion backend/api/cors.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
func Config() gin.HandlerFunc {
return cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
Expand Down
30 changes: 23 additions & 7 deletions backend/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"takumi/internal/config"
"takumi/internal/database"
"takumi/internal/modules/authorization"
"takumi/internal/modules/user"
"takumi/internal/routes"

"github.com/gin-gonic/gin"
Expand All @@ -19,15 +20,11 @@ func main() {
dbHandler := database.InitDB(cfg.DBSource)

app := setupGin(cfg)
app.Static("/uploads", "./uploads")
router := routes.NewTakumiRouter(app, "/api", "/v1")

authService, err := authorization.InitAuthService(dbHandler)
if err != nil {
log.Fatal("error initializing authorization service: ", err)
return
}
authHandlers := authorization.NewHandler(authService)
router.RegisterAuthRoutes(authHandlers)
InitializeModule(dbHandler, authorization.InitAuthService, authorization.NewHandler, router.RegisterAuthRoutes)
InitializeModule(dbHandler, user.InitUserService, user.NewHandler, router.RegisterUserRoutes)

err = app.Run(":" + cfg.Port)
if err != nil {
Expand All @@ -53,3 +50,22 @@ func setupGin(cfg *config.Config) *gin.Engine {

return app
}

type Service interface{}
type Handler interface{}

func InitializeModule[T Service, H Handler](
dbHandler database.DBHandler,
initService func(dbHandler database.DBHandler) (T, error),
createHandler func(T) H,
registerRoutes func(H)) {
service, err := initService(dbHandler)
if err != nil {
log.Fatalf("error initializing service: %v", err)
return
}

handler := createHandler(service)

registerRoutes(handler)
}
2 changes: 1 addition & 1 deletion backend/internal/database/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package database

import (
"log"
"takumi/internal/modules/authorization/types"
"takumi/internal/modules/user/types"

"gorm.io/driver/postgres"
"gorm.io/gorm"
Expand Down
2 changes: 0 additions & 2 deletions backend/internal/modules/authorization/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ func CreateUser(registration *types.User, handler *database.DBHandler) (*types.U
Password: password,
Gender: registration.Gender,
BirthDate: registration.BirthDate,
Role: "USER",
CreatedAt: time.Now(),
Coins: 0,
}

err = handler.DB.Create(user).Error
Expand Down
23 changes: 12 additions & 11 deletions backend/internal/modules/authorization/types/user-model.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package types
import "time"

type User struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
Username string `gorm:"unique;not null" json:"username"`
FirstName string `gorm:"not null" json:"firstName"`
LastName string `gorm:"not null" json:"lastName"`
Email string `gorm:"unique;not null" json:"email"`
Gender string `json:"gender"`
BirthDate time.Time `json:"birthDate"`
Password string `gorm:"not null" json:"password"`
Role string `gorm:"default:USER" json:"role"`
CreatedAt time.Time `json:"createdAt"`
Coins int `gorm:"default:0" json:"coins"`
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
Username string `gorm:"unique;not null" json:"username"`
FirstName string `gorm:"not null" json:"firstName"`
LastName string `gorm:"not null" json:"lastName"`
Email string `gorm:"unique;not null" json:"email"`
Gender string `json:"gender"`
BirthDate time.Time `json:"birthDate"`
Password string `gorm:"not null" json:"password"`
Role string `gorm:"default:USER" json:"role"`
CreatedAt time.Time `json:"createdAt"`
Coins int `gorm:"default:0" json:"coins"`
ProfilePicture string `gorm:"default:''" json:"profilePicture"`
}
167 changes: 167 additions & 0 deletions backend/internal/modules/user/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package user

import (
"fmt"
"log"
"net/http"
"path/filepath"
"strconv"
"takumi/internal/modules/user/types"
"takumi/pkg/utils"

"github.com/gin-gonic/gin"
)

type Handler struct {
Service *Service
}

func NewHandler(service *Service) *Handler {
return &Handler{
Service: service,
}
}

func (h *Handler) GetUserByIDHandler(c *gin.Context) {
userID, err := strconv.Atoi(c.Param("id"))
if err != nil {
utils.SendMessageWithStatus(c, "ERROR: Invalid user ID", http.StatusBadRequest)
return
}

user, err := h.Service.GetUserByID(c, userID)
if err != nil {
utils.SendMessageWithStatus(c, "ERROR: "+err.Error(), http.StatusNotFound)
return
}

utils.SendSuccessJSON(c, user)
}

func (h *Handler) DeleteUserByIDHandler(c *gin.Context) {
userID, err := strconv.Atoi(c.Param("id"))
if err != nil {
utils.SendMessageWithStatus(c, "ERROR: Invalid user ID", http.StatusBadRequest)
return
}

err = h.Service.DeleteUserByID(c, userID)
if err != nil {
utils.SendMessageWithStatus(c, "ERROR: "+err.Error(), http.StatusNotFound)
return
}

utils.SendMessageWithStatus(c, "User deleted successfully", http.StatusOK)
}

func (h *Handler) UpdateUserParamsHandler(c *gin.Context) {
update := &types.User{}
if err := c.ShouldBindJSON(update); err != nil {
utils.SendMessageWithStatus(c, "ERROR: Invalid request body", http.StatusBadRequest)
return
}

updatedUser, err := h.Service.UpdateUserParams(c, *update)
if err != nil {
utils.SendMessageWithStatus(c, "ERROR: "+err.Error(), http.StatusInternalServerError)
return
}

utils.SendSuccessJSON(c, updatedUser)
}

func (h *Handler) PatchUserParamsHandler(c *gin.Context) {
userID, err := strconv.Atoi(c.Param("id"))
if err != nil {
utils.SendMessageWithStatus(c, "ERROR: Invalid user ID", http.StatusBadRequest)
return
}

updateData := map[string]interface{}{}
if err := c.ShouldBindJSON(&updateData); err != nil {
utils.SendMessageWithStatus(c, "ERROR: Invalid request body", http.StatusBadRequest)
return
}

updatedUser, err := h.Service.PatchUserParams(c, userID, updateData)
if err != nil {
utils.SendMessageWithStatus(c, "ERROR: "+err.Error(), http.StatusInternalServerError)
return
}

utils.SendSuccessJSON(c, updatedUser)
}

func (h *Handler) UpdateProfilePictureHandler(c *gin.Context) {
userIDParam := c.Param("id")
userID, err := strconv.Atoi(userIDParam)
if err != nil {
utils.SendMessageWithStatus(c, "ERROR: Invalid user ID", http.StatusBadRequest)
return
}

file, _, err := c.Request.FormFile("profilePicture")
if err != nil {
utils.SendMessageWithStatus(c, "ERROR: File upload failed", http.StatusBadRequest)
return
}
defer file.Close()

fileName := fmt.Sprintf("user_%d_profile_pic.jpg", userID)
if err := utils.SaveFile(file, "profile-pictures", fileName); err != nil {
log.Printf("Failed to save profile picture for user %d: %v", userID, err)
utils.SendMessageWithStatus(c, "ERROR: Could not save profile picture", http.StatusInternalServerError)
return
}

profilePictureURL := filepath.Join("profile-pictures", fileName)
updatedUser, err := h.Service.UpdateProfilePicture(c, userID, profilePictureURL)
if err != nil {
log.Printf("Error updating profile picture for user %d: %v", userID, err)
utils.SendMessageWithStatus(c, err.Error(), http.StatusInternalServerError)
return
}

utils.SendSuccessJSON(c, updatedUser)
}

func (h *Handler) GetProfilePictureByUserID(c *gin.Context) {
userIDParam := c.Param("id")
userID, err := strconv.Atoi(userIDParam)
if err != nil {
utils.SendMessageWithStatus(c, "ERROR: Invalid user ID", http.StatusBadRequest)
return
}

profilePictureURL, err := h.Service.GetProfilePictureByUserID(userID)
if err != nil {
utils.SendMessageWithStatus(c, "ERROR: "+err.Error(), http.StatusInternalServerError)
return
}

utils.SendSuccessJSON(c, gin.H{"profilePictureURL": profilePictureURL})
}

func (h *Handler) DeleteProfilePictureHandler(c *gin.Context) {
userIDParam := c.Param("id")
userID, err := strconv.Atoi(userIDParam)
if err != nil {
utils.SendMessageWithStatus(c, "ERROR: Invalid user ID", http.StatusBadRequest)
return
}

deletedUser, err := h.Service.DeleteProfilePicture(c, userID)

fileName := fmt.Sprintf("user_%d_profile_pic.jpg", userID)
if err := utils.DeleteFile("profile-pictures", fileName); err != nil {
log.Printf("Failed to delete: %s, error: %v", fileName, err)
return
}

if err != nil {
utils.SendMessageWithStatus(c, err.Error(), http.StatusInternalServerError)
return
}

utils.SendSuccessJSON(c, deletedUser)
}
Loading