feat: CR login, register, subscribe

This commit is contained in:
ericprd 2025-02-24 16:48:20 +08:00
parent ae5b2cb975
commit cb3d06fa02
48 changed files with 1027 additions and 59 deletions

View File

@ -5,6 +5,8 @@ import (
"os"
staffmodel "github.com/ardeman/project-legalgo-go/database/staff"
subsmodel "github.com/ardeman/project-legalgo-go/database/subscribe"
usermodel "github.com/ardeman/project-legalgo-go/database/user"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
@ -39,5 +41,10 @@ func NewDB() (*DB, error) {
func (db *DB) Migrate() error {
// Auto Migrate the User model
return db.AutoMigrate(&staffmodel.Staff{})
return db.AutoMigrate(
&staffmodel.Staff{},
&subsmodel.SubscribePlan{},
&subsmodel.Subscribe{},
&usermodel.User{},
)
}

View File

@ -1,11 +1,15 @@
package staffmodel
import (
"time"
"github.com/google/uuid"
)
type Staff struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
Email string `gorm:"unique,not null" json:"email"`
Password string `gorm:"not null" json:"password"`
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
Email string `gorm:"unique,not null" json:"email"`
Password string `gorm:"not null" json:"password"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}

View File

@ -0,0 +1,27 @@
package subsmodel
import (
"time"
"github.com/google/uuid"
)
type Subscribe struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
SubscribePlanID uuid.UUID `gorm:"type:uuid;not null" json:"subscribe_plan_id"`
StartDate time.Time `gorm:"default:CURRENT_TIMESTAMP"`
EndDate time.Time `gorm:"default:null"`
Status string `gorm:"default:'inactive'"`
AutoRenew bool `gorm:"default:true"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
SubscribePlan SubscribePlan `gorm:"foreignKey:SubscribePlanID;constraint:OnDelete:CASCADE"`
}
type SubscribePlan struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
Code string `gorm:"not null" json:"code"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
}

19
database/user/model.go Normal file
View File

@ -0,0 +1,19 @@
package usermodel
import (
"time"
subsmodel "github.com/ardeman/project-legalgo-go/database/subscribe"
"github.com/google/uuid"
)
type User struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
SubscribeID string `gorm:"not null" json:"subscribe_id"`
Email string `gorm:"unique,not null" json:"email"`
Password string `gorm:"not null" json:"password"`
Phone string `gorm:"default:null" json:"phone"`
Subscribe subsmodel.Subscribe `gorm:"foreignKey:SubscribeID;constraint:OnDelete:CASCADE"`
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
}

View File

@ -1,10 +1,18 @@
package repository
import (
redisaccessor "github.com/ardeman/project-legalgo-go/internal/accessor/redis"
staffrepository "github.com/ardeman/project-legalgo-go/internal/accessor/staff"
subscriberepository "github.com/ardeman/project-legalgo-go/internal/accessor/subscribe"
subscribeplanrepository "github.com/ardeman/project-legalgo-go/internal/accessor/subscribeplan"
userrepository "github.com/ardeman/project-legalgo-go/internal/accessor/user_repository"
"go.uber.org/fx"
)
var Module = fx.Module("repository", fx.Provide(
staffrepository.New,
userrepository.New,
redisaccessor.New,
subscribeplanrepository.New,
subscriberepository.New,
))

View File

@ -0,0 +1,32 @@
package redisaccessor
import (
"fmt"
"os"
"github.com/redis/go-redis/v9"
)
var redisClient *redis.Client
func Get() *redis.Client {
return redisClient
}
func New() *redis.Client {
var (
username = os.Getenv("REDIS_USERNAME")
addr = fmt.Sprintf("%s:%s", os.Getenv("REDIS_HOST"), os.Getenv("REDIS_PORT"))
password = os.Getenv("REDIS_PASSWORD")
db = 2 // TODO: change later
)
redisClient = redis.NewClient(&redis.Options{
Username: username,
Addr: addr,
Password: password,
DB: db,
})
return redisClient
}

View File

@ -0,0 +1,11 @@
package staffrepository
import authdomain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
func (ur *StaffRepository) Create(spec *authdomain.Staff) (*authdomain.Staff, error) {
if err := ur.DB.Create(&spec).Error; err != nil {
return nil, err
}
return spec, nil
}

View File

@ -3,12 +3,12 @@ package staffrepository
import (
"errors"
staffmodel "github.com/ardeman/project-legalgo-go/database/staff"
authdomain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
"gorm.io/gorm"
)
func (sr *StaffRepository) GetStaff(email string) (*staffmodel.Staff, error) {
var staff staffmodel.Staff
func (sr *StaffRepository) GetStaffByEmail(email string) (*authdomain.LoginRepoResponse, error) {
var staff authdomain.LoginRepoResponse
if email == "" {
return nil, errors.New("email is empty")

View File

@ -2,17 +2,18 @@ package staffrepository
import (
"github.com/ardeman/project-legalgo-go/database"
staffmodel "github.com/ardeman/project-legalgo-go/database/staff"
authdomain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
)
type StaffRepository struct {
DB *database.DB
}
type StaffInterface interface {
GetStaff(string) (*staffmodel.Staff, error)
type StaffIntf interface {
GetStaffByEmail(string) (*authdomain.LoginRepoResponse, error)
Create(*authdomain.Staff) (*authdomain.Staff, error)
}
func New(db *database.DB) StaffInterface {
func New(db *database.DB) StaffIntf {
return &StaffRepository{db}
}

View File

@ -0,0 +1,19 @@
package subscriberepository
import (
subscribedomain "github.com/ardeman/project-legalgo-go/internal/domain/subscribe"
"github.com/google/uuid"
)
func (s *SubsAccs) Create(subsPlanId string) (string, error) {
spec := &subscribedomain.Subscribe{
ID: uuid.New(),
SubscribePlanID: subsPlanId,
}
if err := s.DB.Create(&spec).Error; err != nil {
return "", err
}
return spec.ID.String(), nil
}

View File

@ -0,0 +1,15 @@
package subscriberepository
import "github.com/ardeman/project-legalgo-go/database"
type SubsAccs struct {
DB *database.DB
}
type SubsIntf interface {
Create(string) (string, error)
}
func New(db *database.DB) SubsIntf {
return &SubsAccs{db}
}

View File

@ -0,0 +1,19 @@
package subscribeplanrepository
import (
subscribeplandomain "github.com/ardeman/project-legalgo-go/internal/domain/subscribe_plan"
"github.com/google/uuid"
)
func (s *SubsPlan) Create(code string) error {
spec := &subscribeplandomain.SubscribePlan{
ID: uuid.New(),
Code: code,
}
if err := s.DB.Create(&spec).Error; err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,17 @@
package subscribeplanrepository
import "github.com/ardeman/project-legalgo-go/database"
type SubsPlan struct {
DB *database.DB
}
type SubsPlanIntf interface {
Create(string) error
}
func New(
db *database.DB,
) SubsPlanIntf {
return &SubsPlan{db}
}

View File

@ -0,0 +1,13 @@
package userrepository
import (
authdomain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
)
func (ur *UserRepository) CreateUser(spec *authdomain.User) (*authdomain.User, error) {
if err := ur.DB.Create(&spec).Error; err != nil {
return nil, err
}
return spec, nil
}

View File

@ -0,0 +1,25 @@
package userrepository
import (
"errors"
usermodel "github.com/ardeman/project-legalgo-go/database/user"
"gorm.io/gorm"
)
func (ur *UserRepository) GetUserByEmail(email string) (*usermodel.User, error) {
var user usermodel.User
if email == "" {
return nil, errors.New("email is empty")
}
if err := ur.DB.Where("email = ?", email).First(&user).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("user not found")
}
return nil, err
}
return &user, nil
}

View File

@ -0,0 +1,22 @@
package userrepository
import (
"github.com/ardeman/project-legalgo-go/database"
usermodel "github.com/ardeman/project-legalgo-go/database/user"
authdomain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
)
type UserRepository struct {
DB *database.DB
}
type UserIntf interface {
GetUserByEmail(string) (*usermodel.User, error)
CreateUser(*authdomain.User) (*authdomain.User, error)
}
func New(
db *database.DB,
) UserIntf {
return &UserRepository{db}
}

View File

@ -3,8 +3,8 @@ package authhttp
import (
"net/http"
domain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
serviceauth "github.com/ardeman/project-legalgo-go/internal/services/auth"
authdomain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
authsvc "github.com/ardeman/project-legalgo-go/internal/services/auth"
"github.com/ardeman/project-legalgo-go/internal/utilities/response"
"github.com/ardeman/project-legalgo-go/internal/utilities/utils"
"github.com/go-chi/chi/v5"
@ -13,15 +13,15 @@ import (
func LoginStaff(
router chi.Router,
authSvc serviceauth.LoginStaffIntf,
authSvc authsvc.AuthIntf,
validate *validator.Validate,
) {
router.Post("/staff/login", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var request domain.StaffLoginReq
var spec authdomain.LoginReq
if err := utils.UnmarshalBody(r, &request); err != nil {
if err := utils.UnmarshalBody(r, &spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
@ -33,7 +33,7 @@ func LoginStaff(
return
}
if err := validate.Struct(request); err != nil {
if err := validate.Struct(spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
@ -45,7 +45,7 @@ func LoginStaff(
return
}
token, err := authSvc.LoginAsStaff(request.Email)
token, err := authSvc.LoginAsStaff(spec)
if err != nil {
response.ResponseWithErrorCode(
ctx,
@ -58,7 +58,62 @@ func LoginStaff(
return
}
responsePayload := &domain.StaffLoginResponse{
responsePayload := &authdomain.LoginResponse{
Token: token,
}
response.RespondJsonSuccess(ctx, w, responsePayload)
})
}
func LoginUser(
router chi.Router,
authSvc authsvc.AuthIntf,
validate *validator.Validate,
) {
router.Post("/user/login", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var spec authdomain.LoginReq
if err := utils.UnmarshalBody(r, &spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
"failed to unmarshal request",
)
return
}
if err := validate.Struct(spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
err.(validator.ValidationErrors).Error(),
)
return
}
token, err := authSvc.LoginAsUser(spec)
if err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
err.Error(),
)
return
}
responsePayload := &authdomain.LoginResponse{
Token: token,
}

View File

@ -5,5 +5,8 @@ import "go.uber.org/fx"
var Module = fx.Module("auth-api",
fx.Invoke(
LoginStaff,
LoginUser,
RegisterUser,
RegisterStaff,
),
)

View File

@ -0,0 +1,114 @@
package authhttp
import (
"net/http"
authdomain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
authsvc "github.com/ardeman/project-legalgo-go/internal/services/auth"
"github.com/ardeman/project-legalgo-go/internal/utilities/response"
"github.com/ardeman/project-legalgo-go/internal/utilities/utils"
"github.com/go-chi/chi/v5"
"github.com/go-playground/validator/v10"
)
func RegisterUser(
router chi.Router,
validate *validator.Validate,
authSvc authsvc.AuthIntf,
) {
router.Post("/user/register", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var spec authdomain.RegisterUserReq
if err := utils.UnmarshalBody(r, &spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
"failed to unmarshal request",
)
return
}
if err := validate.Struct(spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
err.(validator.ValidationErrors).Error(),
)
return
}
token, err := authSvc.RegisterUser(spec)
if err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
err.Error(),
)
return
}
response.RespondJsonSuccess(ctx, w, token)
})
}
func RegisterStaff(
router chi.Router,
validate *validator.Validate,
authSvc authsvc.AuthIntf,
) {
router.Post("/staff/register", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var spec authdomain.RegisterStaffReq
if err := utils.UnmarshalBody(r, &spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
"failed to unmarshal request",
)
return
}
if err := validate.Struct(spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
err.(validator.ValidationErrors).Error(),
)
return
}
token, err := authSvc.RegisterStaff(spec)
if err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
err.Error(),
)
return
}
response.RespondJsonSuccess(ctx, w, token)
})
}

View File

@ -0,0 +1,138 @@
package authmiddleware
// import (
// "context"
// "fmt"
// "net/http"
// "strings"
// redisaccessor "github.com/ardeman/project-legalgo-go/internal/accessor/redis"
// contextkeyenum "github.com/ardeman/project-legalgo-go/internal/enums/context_key"
// jwtclaimenum "github.com/ardeman/project-legalgo-go/internal/enums/jwt"
// resourceenum "github.com/ardeman/project-legalgo-go/internal/enums/resource"
// "github.com/ardeman/project-legalgo-go/internal/services/auth"
// "github.com/golang-jwt/jwt/v5"
// )
// const SessionHeader = "Authorization"
// func Authorization() func(next http.Handler) http.Handler {
// return func(next http.Handler) http.Handler {
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// ctx := r.Context()
// tokenString, err := GetToken(r)
// if err != nil {
// RespondWithError(w, r, err, "Invalid auth header")
// return
// }
// token, err := ValidateToken(ctx, tokenString)
// if err != nil {
// RespondWithError(w, r, err, err.Error())
// return
// }
// if isAuthorized, ctx := VerifyClaims(ctx, token, nil); !isAuthorized {
// RespondWithError(w, r, errorcode.ErrCodeUnauthorized, errorcode.ErrCodeUnauthorized.Message)
// return
// } else {
// next.ServeHTTP(w, r.WithContext(ctx))
// return
// }
// })
// }
// }
// func GetToken(r *http.Request) (string, error) {
// tokenString := GetTokenFromHeader(r)
// if tokenString == "" {
// tokenString = getTokenFromQuery(r)
// }
// if tokenString == "" {
// return "", fmt.Errorf("token not found")
// }
// return tokenString, nil
// }
// func GetTokenFromHeader(r *http.Request) string {
// session := r.Header.Get(SessionHeader)
// arr := strings.Split(session, " ")
// if len(arr) != 2 || strings.ToUpper(arr[0]) != "BEARER" {
// return ""
// }
// return arr[1]
// }
// func getTokenFromQuery(r *http.Request) string {
// token := r.URL.Query().Get("token")
// return token
// }
// func VerifyClaims(ctx context.Context, token *jwt.Token,
// requiredResources []resourceenum.Resource) (bool, context.Context) {
// if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
// rawResources := []interface{}{}
// if claimValue, exist := claims[string(jwtclaimenum.RESOURCES)]; exist {
// rawResources = claimValue.([]interface{})
// }
// resources := []resourceenum.Resource{}
// resourceMap := map[string]bool{}
// for _, v := range rawResources {
// value := v.(string)
// resources = append(resources, resourceenum.Resource(value))
// resourceMap[value] = true
// }
// ctx = context.WithValue(ctx, contextkeyenum.Authorization, UserAuthorization{
// Type: claims[string(jwtclaimenum.TYPE)].(string),
// UserId: claims[string(jwtclaimenum.AUDIENCE)].(string),
// Username: claims[string(jwtclaimenum.USERNAME)].(string),
// Resources: resources,
// })
// isResourceFulfilled := false
// for _, v := range requiredResources {
// if _, ok := resourceMap[string(v)]; ok {
// isResourceFulfilled = true
// ctx = context.WithValue(ctx, contextkeyenum.Resource, v)
// break
// }
// }
// if isResourceFulfilled || len(requiredResources) == 0 {
// return true, ctx
// }
// }
// return false, nil
// }
// func ValidateToken(ctx context.Context, tokenString string) (*jwt.Token, error) {
// redisClient := redisaccessor.Get()
// redisToken, err := redisClient.Exists(ctx, fmt.Sprintf("%s:%s", auth.BLACKLISTED_TOKEN_KEY, tokenString)).Result()
// if err != nil || redisToken > 0 {
// return nil, fmt.Errorf("session already expired")
// }
// token, err := jwt.Parse(tokenString, authsvc.VerifyToken(conf.JWTAccessToken))
// if err != nil {
// if ve, ok := err.(*jwt.ValidationError); ok {
// if ve.Errors&jwt.ValidationErrorExpired != 0 {
// err = errorcode.ErrCodeExpiredToken
// }
// }
// return nil, err
// }
// return token, nil
// }

View File

@ -2,6 +2,7 @@ package internalhttp
import (
authhttp "github.com/ardeman/project-legalgo-go/internal/api/http/auth"
subscribeplanhttp "github.com/ardeman/project-legalgo-go/internal/api/http/subscribe_plan"
"github.com/go-chi/chi/v5"
"github.com/go-chi/cors"
"github.com/go-playground/validator/v10"
@ -16,6 +17,7 @@ var Module = fx.Module("router",
validator.New,
),
authhttp.Module,
subscribeplanhttp.Module,
)
func initRouter() chi.Router {

View File

@ -0,0 +1,66 @@
package subscribeplanhttp
import (
"net/http"
subscribeplandomain "github.com/ardeman/project-legalgo-go/internal/domain/subscribe_plan"
subscribeplansvc "github.com/ardeman/project-legalgo-go/internal/services/subscribe_plan"
"github.com/ardeman/project-legalgo-go/internal/utilities/response"
"github.com/ardeman/project-legalgo-go/internal/utilities/utils"
"github.com/go-chi/chi/v5"
"github.com/go-playground/validator/v10"
)
func CreateSubscribePlan(
router chi.Router,
validate *validator.Validate,
subsSvc subscribeplansvc.SubsPlanIntf,
) {
router.Post("/subscribe-plan/create", func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var spec subscribeplandomain.SubscribePlanReq
if err := utils.UnmarshalBody(r, &spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
"failed to unmarshal request",
)
return
}
if err := validate.Struct(spec); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrBadRequest.Code,
response.ErrBadRequest.HttpCode,
err.(validator.ValidationErrors).Error(),
)
return
}
if err := subsSvc.CreatePlan(spec.Code); err != nil {
response.ResponseWithErrorCode(
ctx,
w,
err,
response.ErrCreateEntity.Code,
response.ErrCreateEntity.HttpCode,
err.Error(),
)
return
}
response.RespondJsonSuccess(ctx, w, struct {
Message string
}{
Message: "success",
})
})
}

View File

@ -0,0 +1,9 @@
package subscribeplanhttp
import "go.uber.org/fx"
var Module = fx.Module("subscribe-plan",
fx.Invoke(
CreateSubscribePlan,
),
)

View File

@ -16,7 +16,7 @@ import (
func Router(apiRouter chi.Router) {
mainRouter := chi.NewRouter()
mainRouter.Mount("/", apiRouter)
mainRouter.Mount("/api", apiRouter)
mainCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()

View File

@ -0,0 +1,18 @@
package authdomain
import "github.com/google/uuid"
type LoginReq struct {
Email string `json:"email" validate:"required"`
Password string `json:"password" validate:"required"`
}
type LoginResponse struct {
Token string `json:"token"`
}
type LoginRepoResponse struct {
ID uuid.UUID `json:"id"`
Email string `json:"email"`
Password string `json:"password"`
}

View File

@ -0,0 +1,29 @@
package authdomain
import "github.com/google/uuid"
type RegisterUserReq struct {
Email string `json:"email" validate:"required"`
Password string `json:"password" validate:"required"`
Phone string `json:"phone"`
SubscribePlanID string `json:"subscribe_plan_id"`
}
type User struct {
ID uuid.UUID `json:"id"`
Email string `json:"email"`
Password string `json:"password"`
Phone string `json:"phone"`
SubscribeID string `json:"subscribe_id"`
}
type RegisterStaffReq struct {
Email string `json:"email" validate:"required"`
Password string `json:"password" validate:"required"`
}
type Staff struct {
ID uuid.UUID `json:"id"`
Email string `json:"email" validate:"required"`
Password string `json:"password" validate:"required"`
}

View File

@ -1,10 +0,0 @@
package domain
type StaffLoginReq struct {
Email string `json:"email" validate:"required"`
Password string `json:"password" validate:"required"`
}
type StaffLoginResponse struct {
Token string `json:"token"`
}

View File

@ -0,0 +1,9 @@
package cachingdomain
import "time"
type CacheSpec struct {
Key string
TTL time.Duration
Data any
}

View File

@ -0,0 +1,8 @@
package subscribedomain
import "github.com/google/uuid"
type Subscribe struct {
ID uuid.UUID `json:"id"`
SubscribePlanID string `json:"subscribe_plan_id"`
}

View File

@ -0,0 +1,12 @@
package subscribeplandomain
import "github.com/google/uuid"
type SubscribePlanReq struct {
Code string `json:"code" validate:"required"`
}
type SubscribePlan struct {
ID uuid.UUID `json:"id"`
Code string `json:"code"`
}

View File

@ -0,0 +1,8 @@
package contextkeyenum
type ContextKey string
const (
Authorization ContextKey = "AUTHORIZATION_CONTEXT"
Resource ContextKey = "RESOURCE_CONTEXT"
)

View File

@ -8,4 +8,5 @@ const (
EXPIRED_AT JWTClaim = "exp"
SESSION_ID JWTClaim = "sid"
ISSUED_AT JWTClaim = "iat"
RESOURCES JWTClaim = "resources"
)

View File

@ -0,0 +1,3 @@
package resourceenum
type Resource string

View File

@ -1,19 +1,33 @@
package serviceauth
package authsvc
import staffrepository "github.com/ardeman/project-legalgo-go/internal/accessor/staff"
import (
staffrepository "github.com/ardeman/project-legalgo-go/internal/accessor/staff"
subscriberepository "github.com/ardeman/project-legalgo-go/internal/accessor/subscribe"
userrepository "github.com/ardeman/project-legalgo-go/internal/accessor/user_repository"
authdomain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
)
type LoginStaffSvc struct {
staffAcs staffrepository.StaffInterface
type AuthSvc struct {
staffRepo staffrepository.StaffIntf
userRepo userrepository.UserIntf
subsRepo subscriberepository.SubsIntf
}
type LoginStaffIntf interface {
LoginAsStaff(string) (string, error)
type AuthIntf interface {
LoginAsStaff(authdomain.LoginReq) (string, error)
LoginAsUser(authdomain.LoginReq) (string, error)
RegisterUser(authdomain.RegisterUserReq) (string, error)
RegisterStaff(authdomain.RegisterStaffReq) (string, error)
}
func New(
staffAcs staffrepository.StaffInterface,
) LoginStaffIntf {
return &LoginStaffSvc{
staffAcs: staffAcs,
staffRepo staffrepository.StaffIntf,
userRepo userrepository.UserIntf,
subsRepo subscriberepository.SubsIntf,
) AuthIntf {
return &AuthSvc{
staffRepo: staffRepo,
userRepo: userRepo,
subsRepo: subsRepo,
}
}

View File

@ -1,18 +1,24 @@
package serviceauth
package authsvc
import (
"errors"
authdomain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
"github.com/ardeman/project-legalgo-go/internal/utilities/utils"
)
func (sv *LoginStaffSvc) LoginAsStaff(email string) (string, error) {
staff, err := sv.staffAcs.GetStaff(email)
func (sv *AuthSvc) LoginAsStaff(spec authdomain.LoginReq) (string, error) {
staff, err := sv.staffRepo.GetStaffByEmail(spec.Email)
if err != nil {
return "", errors.New(err.Error())
}
token, err := utils.GenerateToken2(staff.Email)
matchPassword := ComparePassword(staff.Password, spec.Password)
if !matchPassword {
return "", errors.New("wrong password")
}
token, err := utils.GenerateToken(staff.Email)
if err != nil {
return "", errors.New(err.Error())
}

View File

@ -0,0 +1,27 @@
package authsvc
import (
"errors"
authdomain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
"github.com/ardeman/project-legalgo-go/internal/utilities/utils"
)
func (sv *AuthSvc) LoginAsUser(spec authdomain.LoginReq) (string, error) {
user, err := sv.userRepo.GetUserByEmail(spec.Email)
if err != nil {
return "", errors.New(err.Error())
}
matchPassword := ComparePassword(user.Password, spec.Password)
if !matchPassword {
return "", errors.New("wrong password")
}
token, err := utils.GenerateToken(user.Email)
if err != nil {
return "", errors.New(err.Error())
}
return token, nil
}

View File

@ -0,0 +1,33 @@
package authsvc
import (
"errors"
authdomain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
"github.com/ardeman/project-legalgo-go/internal/utilities/utils"
"github.com/google/uuid"
)
func (a *AuthSvc) RegisterStaff(spec authdomain.RegisterStaffReq) (string, error) {
hashedPwd, err := HashPassword(spec.Password)
if err != nil {
return "", err
}
user := authdomain.Staff{
ID: uuid.New(),
Email: spec.Email,
Password: hashedPwd,
}
_, err = a.staffRepo.Create(&user)
if err != nil {
return "", errors.New(err.Error())
}
token, err := utils.GenerateToken(spec.Email)
if err != nil {
return "", errors.New(err.Error())
}
return token, nil
}

View File

@ -0,0 +1,40 @@
package authsvc
import (
"errors"
authdomain "github.com/ardeman/project-legalgo-go/internal/domain/auth"
"github.com/ardeman/project-legalgo-go/internal/utilities/utils"
"github.com/google/uuid"
)
func (a *AuthSvc) RegisterUser(spec authdomain.RegisterUserReq) (string, error) {
subsId, err := a.subsRepo.Create(spec.SubscribePlanID)
if err != nil {
return "", nil
}
hashedPwd, err := HashPassword(spec.Password)
if err != nil {
return "", err
}
user := authdomain.User{
ID: uuid.New(),
Email: spec.Email,
SubscribeID: subsId,
Password: hashedPwd,
Phone: spec.Phone,
}
_, err = a.userRepo.CreateUser(&user)
if err != nil {
return "", errors.New(err.Error())
}
token, err := utils.GenerateToken(spec.Email)
if err != nil {
return "", errors.New(err.Error())
}
return token, nil
}

View File

@ -0,0 +1,32 @@
package authsvc
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
)
func VerifyToken(signature string) jwt.Keyfunc {
return func(token *jwt.Token) (any, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(signature), nil
}
}
func HashPassword(password string) (string, error) {
// Hashing the password with a cost of 14 (default)
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hash), nil
}
func ComparePassword(storedPassword, inputPassword string) bool {
err := bcrypt.CompareHashAndPassword([]byte(storedPassword), []byte(inputPassword))
return err == nil
}

View File

@ -0,0 +1,20 @@
package cachingsvc
import (
"context"
cachingdomain "github.com/ardeman/project-legalgo-go/internal/domain/caching"
"github.com/redis/go-redis/v9"
)
type impl struct {
redisClient *redis.Client
}
type implIntf interface {
Set(context.Context, cachingdomain.CacheSpec) error
}
func New() implIntf {
return &impl{}
}

View File

@ -0,0 +1,31 @@
package cachingsvc
import (
"context"
"encoding/json"
cachingdomain "github.com/ardeman/project-legalgo-go/internal/domain/caching"
"github.com/ardeman/project-legalgo-go/internal/utilities/response"
"github.com/sirupsen/logrus"
)
func (i *impl) Set(ctx context.Context, spec cachingdomain.CacheSpec) error {
redisClient := i.redisClient
if redisClient == nil {
logrus.WithContext(ctx).Errorf("redis not found")
return response.ErrRedisConnNotFound
}
dataBytes, err := json.Marshal(spec.Data)
if err != nil {
return response.ErrMarshal
}
if err := redisClient.Set(ctx, spec.Key, string(dataBytes), spec.TTL); err != nil {
return response.ErrRedisSet
}
return nil
}

View File

@ -2,11 +2,15 @@ package services
import (
serviceauth "github.com/ardeman/project-legalgo-go/internal/services/auth"
subscribesvc "github.com/ardeman/project-legalgo-go/internal/services/subscribe"
subscribeplansvc "github.com/ardeman/project-legalgo-go/internal/services/subscribe_plan"
"go.uber.org/fx"
)
var Module = fx.Module("services",
fx.Provide(
serviceauth.New,
subscribeplansvc.New,
subscribesvc.New,
),
)

View File

@ -0,0 +1,9 @@
package subscribesvc
func (s *SubsSvc) Create(subsPlanId string) (string, error) {
subsId, err := s.subsRepo.Create(subsPlanId)
if err != nil {
return "", err
}
return subsId, nil
}

View File

@ -0,0 +1,15 @@
package subscribesvc
import subscriberepository "github.com/ardeman/project-legalgo-go/internal/accessor/subscribe"
type SubsSvc struct {
subsRepo subscriberepository.SubsIntf
}
type SubsIntf interface {
Create(string) (string, error)
}
func New(subsRepo subscriberepository.SubsIntf) SubsIntf {
return &SubsSvc{subsRepo}
}

View File

@ -0,0 +1,5 @@
package subscribeplansvc
func (sb *SubsPlanSvc) CreatePlan(code string) error {
return sb.subsAccs.Create(code)
}

View File

@ -0,0 +1,17 @@
package subscribeplansvc
import subscribeplanrepository "github.com/ardeman/project-legalgo-go/internal/accessor/subscribeplan"
type SubsPlanSvc struct {
subsAccs subscribeplanrepository.SubsPlanIntf
}
type SubsPlanIntf interface {
CreatePlan(string) error
}
func New(
subsAccs subscribeplanrepository.SubsPlanIntf,
) SubsPlanIntf {
return &SubsPlanSvc{subsAccs}
}

View File

@ -8,8 +8,20 @@ type ErrorCode struct {
HttpCode int
}
func (e ErrorCode) Error() string {
return e.Code
}
var (
// 4xx
ErrBadRequest = ErrorCode{Code: "BAD_REQUEST", Message: "BAD_REQUEST", HttpCode: http.StatusBadRequest}
ErrDBRequest = ErrorCode{Code: "BAD_DB_REQUEST", Message: "DB_ERROR", HttpCode: http.StatusBadRequest}
ErrBadRequest = &ErrorCode{Code: "BAD_REQUEST", Message: "BAD_REQUEST", HttpCode: http.StatusBadRequest}
ErrDBRequest = &ErrorCode{Code: "BAD_DB_REQUEST", Message: "DB_ERROR", HttpCode: http.StatusBadRequest}
// 5xx
ErrMarshal = &ErrorCode{Code: "FAILED_MARSHAL", Message: "FAILED_MARSHAL_BODY", HttpCode: http.StatusInternalServerError}
ErrCreateEntity = &ErrorCode{Code: "FAILED_CREATE_ENTITY", Message: "FAILED_CREATE_ENTITY", HttpCode: http.StatusInternalServerError}
// redis
ErrRedisConnNotFound = &ErrorCode{Code: "FAILED_CONNECT_REDIS", Message: "REDIS_NOT_FOUND", HttpCode: http.StatusInternalServerError}
ErrRedisSet = &ErrorCode{Code: "FAILED_SET_REDIS", Message: "FAILED_CACHING", HttpCode: http.StatusInternalServerError}
)

View File

@ -3,7 +3,6 @@ package utils
import (
"time"
jwtclaimenum "github.com/ardeman/project-legalgo-go/internal/enums/jwt"
timeutils "github.com/ardeman/project-legalgo-go/internal/utilities/time_utils"
"github.com/golang-jwt/jwt/v5"
)
@ -12,23 +11,23 @@ var jwtSecret = []byte("secret jwt key") // TODO: change later from env
type ClaimOption func(options jwt.MapClaims)
func GenerateToken(options ...ClaimOption) (string, error) {
now := timeutils.Now()
// func GenerateToken(options ...ClaimOption) (string, error) {
// now := timeutils.Now()
claims := jwt.MapClaims{
string(jwtclaimenum.ISSUED_AT): now.Unix(),
}
// claims := jwt.MapClaims{
// string(jwtclaimenum.ISSUED_AT): now.Unix(),
// }
for _, o := range options {
o(claims)
}
// for _, o := range options {
// o(claims)
// }
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
// return token.SignedString(jwtSecret)
// }
func GenerateToken2(email string) (string, error) {
func GenerateToken(email string) (string, error) {
now := timeutils.Now()
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)