Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement storing profile picture on cloud service #2323

Open
wants to merge 1 commit into
base: aait.backend.g11.main
Choose a base branch
from
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
27 changes: 15 additions & 12 deletions backend/AAiT-backend-group-11/bootstrap/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ import (

// Env is a struct to hold environment variables.
type Env struct {
AppEnv string `mapstructure:"APP_ENV"`
ContextTimeout int `mapstructure:"CONTEXT_TIMEOUT"`
DBHost string `mapstructure:"DB_HOST"`
DBPort string `mapstructure:"DB_PORT"`
DBName string `mapstructure:"DB_NAME"`
DBUri string `mapstructure:"MONGODB_URI"`
AccessTokenExpiryHour int `mapstructure:"ACCESS_TOKEN_EXPIRY_HOUR"`
RefreshTokenExpiryHour int `mapstructure:"REFRESH_TOKEN_EXPIRY_HOUR"`
AccessTokenSecret string `mapstructure:"ACCESS_TOKEN_SECRET"`
RefreshTokenSecret string `mapstructure:"REFRESH_TOKEN_SECRET"`
PasswordResetSecret string `mapstructure:"PASSWORD_RESET_SECRET"`
GeminiApiKey string `mapstructure:"GEMINI_API_KEY"`
AppEnv string `mapstructure:"APP_ENV"`
ContextTimeout int `mapstructure:"CONTEXT_TIMEOUT"`
DBHost string `mapstructure:"DB_HOST"`
DBPort string `mapstructure:"DB_PORT"`
DBName string `mapstructure:"DB_NAME"`
DBUri string `mapstructure:"MONGODB_URI"`
AccessTokenExpiryHour int `mapstructure:"ACCESS_TOKEN_EXPIRY_HOUR"`
RefreshTokenExpiryHour int `mapstructure:"REFRESH_TOKEN_EXPIRY_HOUR"`
AccessTokenSecret string `mapstructure:"ACCESS_TOKEN_SECRET"`
RefreshTokenSecret string `mapstructure:"REFRESH_TOKEN_SECRET"`
PasswordResetSecret string `mapstructure:"PASSWORD_RESET_SECRET"`
GeminiApiKey string `mapstructure:"GEMINI_API_KEY"`
RedisAddress string `mapstructure:"REDIS_ADDRESS"`
CloudName string `mapstructure:"CLOUD_NAME"`
ApiKey string `mapstructure:"CLOUD_API_KEY"`
ApiSecret string `mapstructure:"CLOUD_API_SECRET"`
}

// NewEnv initializes and returns a new instance of the Env struct.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,27 +73,35 @@ func (controller *AuthController) Login(c *gin.Context) {
}

c.Header("Authorization", "Bearer "+accessToken)
c.SetCookie("refresh_token", refreshToken.Token, int(refreshToken.ExpiresAt.Unix()), "/", "localhost", false, true)
c.Set("userId", refreshToken.UserID)

c.JSON(200, gin.H{"access_token": accessToken})
c.JSON(200, gin.H{"refresh_token": refreshToken.Token})
c.JSON(200, gin.H{"message": "login successful"})
c.Set("userId", refreshToken.UserID)
c.SetCookie("refresh_token", refreshToken.Token, int(refreshToken.ExpiresAt.Unix()), "/", "localhost", false, true)
}

func (controller *AuthController) Logout(c *gin.Context) {
userId := c.GetString("userId")

controller.authService.Logout(userId)
err:=controller.authService.Logout(userId)
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.SetCookie("refresh_token", "", -1, "/", "localhost", false, true)
c.Header("Authorization", "")
c.JSON(200, gin.H{"message": "succesfully logged out"})
}

func (controller *AuthController) RefreshAccessToken(c *gin.Context) {
var token entities.RefreshToken
err := c.ShouldBindJSON(&token)
refresh,err:=c.Cookie("refresh_token")

if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
controller.authService.RefreshAccessToken(&token)
controller.authService.RefreshAccessToken(refresh)
}

func (controller *AuthController) VerifyEmail(c *gin.Context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ type ProfileController struct {
func NewProfileController(service interfaces.ProfileService) ProfileController {
return ProfileController{ProfileService: service}
}
func (controller *ProfileController) GetAllProfiles(ctx *gin.Context) {
profiles, err := controller.ProfileService.GetAllProfiles()
if err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
ctx.JSON(200, profiles)
}

func (controller *ProfileController) CreateUserProfile(ctx *gin.Context) {
var profile dto.CreateProfileDto
Expand All @@ -28,18 +36,17 @@ func (controller *ProfileController) CreateUserProfile(ctx *gin.Context) {
return
}
profile.UserID = userID

profile_, err := controller.ProfileService.CreateUserProfile(&profile)
if err!=nil{
if err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
ctx.JSON(200, gin.H{"message": "Profile created successfully", "profile": profile_})

}

func (controller *ProfileController) GetUserProfile(ctx *gin.Context) {
userId:=ctx.Param("userId")
userId := ctx.Param("userId")
profile, err := controller.ProfileService.GetUserProfile(userId)
if err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
Expand All @@ -56,10 +63,10 @@ func (controller *ProfileController) UpdateUserProfile(ctx *gin.Context) {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
updUserId:=ctx.Param("userId")
updUserId := ctx.Param("userId")

UserID := ctx.GetString("userId")
if UserID!=updUserId {
if UserID != updUserId {
ctx.JSON(400, gin.H{"error": "You are only authorized to update your own profile"})
return
}
Expand All @@ -73,10 +80,11 @@ func (controller *ProfileController) UpdateUserProfile(ctx *gin.Context) {
}

func (controller *ProfileController) DeleteUserProfile(ctx *gin.Context) {
delId:=ctx.Param("userId")
delId := ctx.Param("userId")
userId := ctx.GetString("userId")
if userId!=delId {
ctx.JSON(400, gin.H{"error": "You are only authorized to delete your own profile"})
if userId != delId {

ctx.JSON(400, gin.H{"error": "You are only authorized to delete your own profile", "userId": userId, "delId": delId})
return
}
err := controller.ProfileService.DeleteUserProfile(userId)
Expand All @@ -86,3 +94,45 @@ func (controller *ProfileController) DeleteUserProfile(ctx *gin.Context) {
}
ctx.JSON(200, gin.H{"message": "Profile deleted successfully"})
}

func (controller *ProfileController) UpdateOrCreateProfilePicture(ctx *gin.Context) {
userId := ctx.GetString("userId")
image, err := ctx.FormFile("image")
if err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}

url, err := controller.ProfileService.UpdateProfilePicture(userId, image)
if err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
ctx.JSON(200, gin.H{"message": "Profile picture updated successfully", "url": url})

}

func (controller *ProfileController) GetProfilePicture(ctx *gin.Context) {
userId := ctx.Param("userId")
url, err := controller.ProfileService.GetProfilePicture(userId)
if err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
ctx.JSON(200, gin.H{"url": url})
}

func (controller *ProfileController) DeleteProfilePicture(ctx *gin.Context) {
userId := ctx.GetString("userId")
user_id := ctx.Param("userId")
if userId != user_id {
ctx.JSON(400, gin.H{"error": "You cann't delete others profile picture", "userId": userId, "user_id": user_id})
return
}
err := controller.ProfileService.DeleteProfilePicture(userId)
if err != nil {
ctx.JSON(400, gin.H{"error": err.Error()})
return
}
ctx.JSON(200, gin.H{"message": "Profile picture deleted successfully"})
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func NewAuthRouter(env *bootstrap.Env, db *mongo.Database, group *gin.RouterGrou

group.POST("login", auth_controller.Login)
group.POST("logout", auth_controller.Logout)
group.POST("refresh", auth_controller.RefreshAccessToken)
group.GET("refresh", auth_controller.RefreshAccessToken)
group.POST("register", auth_controller.RegisterUser)
group.POST("/verify-email", auth_controller.VerifyEmail)
group.POST("/forgot-password", auth_controller.RequestPasswordReset)
Expand Down
22 changes: 19 additions & 3 deletions backend/AAiT-backend-group-11/delivery/router/profile_router.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package route

import (
"backend-starter-project/bootstrap"
"backend-starter-project/delivery/controller"
"backend-starter-project/infrastructure"
"backend-starter-project/repository"
"backend-starter-project/service"
"context"
Expand All @@ -10,14 +12,28 @@ import (
"go.mongodb.org/mongo-driver/mongo"
)

func NewProfileRouter(db *mongo.Database, group *gin.RouterGroup) {
func NewProfileRouter(env *bootstrap.Env,db *mongo.Database, group *gin.RouterGroup) {
cloudName := env.CloudName
apiKey := env.ApiKey
apiSecret := env.ApiSecret

imageService,err:= infrastructure.NewImageService(cloudName,apiKey,apiSecret)
if err != nil {
panic(err)
}


profile_repo := repository.NewProfileRepository(context.TODO(), db)
profile_service := service.NewProfileService(profile_repo)
profile_service := service.NewProfileService(profile_repo,imageService)
profile_controller := controller.NewProfileController(profile_service)

group.GET("/profile/:userId", profile_controller.GetUserProfile)
group.POST("/profile", profile_controller.CreateUserProfile)
group.PUT("/profile/:userId", profile_controller.UpdateUserProfile)
group.DELETE("/profile/:userId", profile_controller.DeleteUserProfile)

group.POST("/profile/profilePicture", profile_controller.UpdateOrCreateProfilePicture)
group.PUT("/profile/profilePicture", profile_controller.UpdateOrCreateProfilePicture)
group.GET("/profile/profilePicture/:userId", profile_controller.GetProfilePicture)
group.DELETE("/profile/profilePicture/:userId", profile_controller.DeleteProfilePicture)
group.GET("/profile", profile_controller.GetAllProfiles)
}
3 changes: 1 addition & 2 deletions backend/AAiT-backend-group-11/delivery/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ func Setup(env *bootstrap.Env, db *mongo.Database, gin *gin.Engine, auth middlew
NewBlogRouter(db, privateRouter.Group("/blogs"), model, redis)
NewCommmentRouter(db, privateRouter.Group("/comments"))
NewAuthRouter(env,db, publicRouter.Group("/auth"))
NewProfileRouter(db, privateRouter.Group("/user"))
NewUserRouter(db, adminRouter.Group("/user"))
NewProfileRouter(env,db, privateRouter.Group("/user"))

gin.Run(":8080")
}
15 changes: 13 additions & 2 deletions backend/AAiT-backend-group-11/domain/dto/profile_dto.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package dto

import "backend-starter-project/domain/entities"

type CreateProfileDto struct {
UserID string `json:"-"`
Bio string `json:"bio"`
Expand All @@ -13,5 +15,14 @@ type UpdateProfileDto struct {
UserID string `json:"-"`
Bio string `json:"bio"`
ProfilePicture string `json:"profilePicture"`
Address string `json:"address"`
}
Address string `json:"address"`
}

type ProfileResponse struct {
ID string `json:"id"`
UserID string `json:"userId"`
Bio string `json:"bio"`
ProfilePicture string `json:"profilePicture"`
ContactInfo entities.ContactInfo `json:"contactInfo"`
}

2 changes: 1 addition & 1 deletion backend/AAiT-backend-group-11/domain/interfaces/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type AuthenticationService interface {
RegisterUser(user *entities.User) (*entities.User, error)
Login(emailOrUsername, password string) (*entities.RefreshToken,string, error)
Logout(userId string) error
RefreshAccessToken(token *entities.RefreshToken) (string,error)
RefreshAccessToken(token string) (string,error)
VerifyEmail(email string, code string) error
ResendOtp(request entities.ResendOTPRequest) error
}
Expand Down
20 changes: 17 additions & 3 deletions backend/AAiT-backend-group-11/domain/interfaces/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,32 @@ package interfaces
import (
"backend-starter-project/domain/dto"
"backend-starter-project/domain/entities"
"mime/multipart"
)

type ProfileRepository interface {
GetAllProfiles() ([]*entities.Profile, error)
GetUserProfile(userId string) (*entities.Profile, error)
UpdateUserProfile(profile *entities.Profile) (*entities.Profile, error)
CreateUserProfile(profile *entities.Profile) (*entities.Profile, error)
DeleteUserProfile(user_id string) error
UpdateProfilePicture(user_id,path string) error
GetProfilePicture(user_id string) (string,error)
DeleteProfilePicture(user_id string) error
}

type ProfileService interface {
GetUserProfile(userId string) (*entities.Profile, error)
UpdateUserProfile(profile *dto.UpdateProfileDto) (*entities.Profile, error)
CreateUserProfile(profile *dto.CreateProfileDto) (*entities.Profile, error)
GetAllProfiles() ([]*entities.Profile, error)
GetUserProfile(userId string) (*dto.ProfileResponse, error)
UpdateUserProfile(profile *dto.UpdateProfileDto) (*dto.ProfileResponse, error)
CreateUserProfile(profile *dto.CreateProfileDto) (*dto.ProfileResponse, error)
DeleteUserProfile(user_id string) error
UpdateProfilePicture(user_id string,file *multipart.FileHeader) (string,error)
GetProfilePicture(user_id string) (string,error)
DeleteProfilePicture(user_id string) error
}

type ImageService interface {
UploadImage(file *multipart.FileHeader) (string, error)
DeleteImage(path string) error
}
5 changes: 3 additions & 2 deletions backend/AAiT-backend-group-11/domain/interfaces/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ type TokenService interface {
VerifyRefreshToken(token string) error
InvalidateAccessToken(token string) (string, error)
InvalidateRefreshToken(token string) (string, error)
GetClaimsFromToken(token string) map[string]string
RefreshAccessToken(token *entities.RefreshToken) (string,error)
GetClaimsFromAccessToken(token string) map[string]string
GetClaimsFromRefreshToken(token string) map[string]string
RefreshAccessToken(accToken string) (string,error)
CreateRefreshToken(refreshToken *entities.RefreshToken) (*entities.RefreshToken, error)
DeleteRefreshTokenByUserId(userId string) error
FindRefreshTokenByUserId(userId string) (*entities.RefreshToken, error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package middleware
import (
"backend-starter-project/domain/interfaces"
"errors"
"fmt"
"log"
"net/http"
"strings"
Expand All @@ -27,8 +28,10 @@ func (middleware *authMiddleware) AuthMiddleware(role string) gin.HandlerFunc {
header := c.GetHeader("Authorization")
refresh, err := c.Cookie("refresh_token")
if err != nil {
fmt.Println("error from auth middleware: second err", err)
err := middleware.TokenService.VerifyRefreshToken(refresh)
if err != nil {
fmt.Println("error from auth middleware: first err", err)
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
c.Abort()
return
Expand Down Expand Up @@ -58,12 +61,12 @@ func (middleware *authMiddleware) AuthMiddleware(role string) gin.HandlerFunc {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
c.Abort()
}

fmt.Println("Error from auth middleware: ", err)
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
c.Abort()
return
}
claims := middleware.TokenService.GetClaimsFromToken(authParts[1])
claims := middleware.TokenService.GetClaimsFromAccessToken(authParts[1])
if role != "" {
if claims["role"] != role {
c.JSON(http.StatusForbidden, gin.H{"error": "Insufficient permissions"})
Expand Down
Loading