Skip to content
Merged
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: 2 additions & 2 deletions ci.override.env
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
RESERVATION_SERVICE_PATH=./services/reservation-service
RESERVATION_SERVICE_REPO=https://github.com/book-em/reservation-service.git
RESERVATION_SERVICE_BRANCH=develop
RESERVATION_SERVICE_BRANCH=feature-delete-user-complete

ROOM_SERVICE_PATH=./services/room-service
ROOM_SERVICE_REPO=https://github.com/book-em/room-service.git
ROOM_SERVICE_BRANCH=develop
ROOM_SERVICE_BRANCH=feature-delete-user-complete
3 changes: 3 additions & 0 deletions compose.integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ services:
condition: service_healthy
reservation-service:
condition: service_healthy
environment:
JWT_PUBLIC_KEY_PATH: /app/keys/public_key.pem
volumes:
- go-mod-cache:/go/pkg/mod
- ${USER_SERVICE_PATH}/keys/public_key.pem:/app/keys/public_key.pem:ro

user-service:
build:
Expand Down
2 changes: 2 additions & 0 deletions src/api/middleware/error_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func mapErrorToStatus(err error) int {
return http.StatusNotFound
case errors.Is(err, domain.ErrWrongPassword):
return http.StatusUnauthorized
case errors.Is(err, domain.ErrDeletedAccount):
return http.StatusUnauthorized
default:
return http.StatusInternalServerError
}
Expand Down
44 changes: 44 additions & 0 deletions src/api/middleware/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package middleware

import (
"fmt"
"os"

"github.com/golang-jwt/jwt/v5"
)

var JWT_PUBLIC_KEY_PATH = os.Getenv("JWT_PUBLIC_KEY_PATH")

var ParseJWT = parseJWT

// ParseJWT validates and extracts claims from an encoded JWT string.
func parseJWT(tokenString string) (jwt.MapClaims, error) {
publicKeyData, err := os.ReadFile(JWT_PUBLIC_KEY_PATH)
if err != nil {
return nil, fmt.Errorf("could not open public key %s: %w", JWT_PUBLIC_KEY_PATH, err)
}

publicKey, err := jwt.ParseRSAPublicKeyFromPEM(publicKeyData)
if err != nil {
return nil, fmt.Errorf("could not parse public key: %w", err)
}

token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method")
}
return publicKey, nil
})

if err != nil {
return nil, err
}

claims, ok := token.Claims.(jwt.MapClaims)

if !ok {
return nil, fmt.Errorf("invalid jwt token or claims")
}

return claims, nil
}
15 changes: 15 additions & 0 deletions src/api/middleware/jwt_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,18 @@ func GetJwt(ctx *gin.Context) (*Jwt, error) {

return &jwt, nil
}

func GetJwtFromString(jwtString string) (*Jwt, error) {
jwtData, err := ParseJWT(jwtString)
if err != nil {
return nil, err
}

jwt := Jwt{
ID: uint(jwtData["sub"].(float64)),
Username: jwtData["username"].(string),
Role: domain.UserRole(jwtData["role"].(string)),
}

return &jwt, nil
}
19 changes: 19 additions & 0 deletions src/client/reservationclient/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,22 @@ type ReservationDTO struct {
Cancelled bool `gorm:"not null"`
Cost uint `gorm:"not null"`
}

type CreateReservationRequestDTO struct {
RoomID uint `json:"roomId"`
DateFrom time.Time `json:"dateFrom"`
DateTo time.Time `json:"dateTo"`
GuestCount uint `json:"guestCount"`
}

type ReservationRequestDTO struct {
ID uint `json:"id"`
RoomID uint `json:"roomId"`
DateFrom time.Time `json:"dateFrom"`
DateTo time.Time `json:"dateTo"`
GuestCount uint `json:"guestCount"`
GuestID uint `json:"guestId"`
Status string `json:"status"`
Cost uint `json:"cost"`
GuestCancelCount uint `json:"guestCancelCount"`
}
44 changes: 43 additions & 1 deletion src/client/roomclient/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
package roomclient

import (
utils "bookem-user-service/util"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
)

type RoomClient interface {
DeleteHostRooms(ctx context.Context, jwt string) ([]RoomDTO, error)
}

type roomClient struct {
Expand All @@ -9,6 +19,38 @@ type roomClient struct {

func NewRoomClient() RoomClient {
return &roomClient{
baseURL: "http://room-service:8080/api", // TODO: This should not be hardcoded
baseURL: "http://room-service:8080/api/", // TODO: This should not be hardcoded
}
}

func (c *roomClient) DeleteHostRooms(ctx context.Context, jwt string) ([]RoomDTO, error) {
utils.TEL.Push(ctx, "delete-host-rooms")
defer utils.TEL.Pop()

req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%shost/", c.baseURL), nil)
if err != nil {
utils.TEL.Error("preparing request error ", err)
return nil, err
}
req.Header.Add("Authorization", "Bearer "+jwt)
resp, err := http.DefaultClient.Do(req)

if err != nil {
utils.TEL.Error("request error ", err)
return nil, err
}

bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
utils.TEL.Error("parsing response error", err)
return nil, err
}

var obj []RoomDTO
if err := json.Unmarshal(bodyBytes, &obj); err != nil {
utils.TEL.Error("JSON unmarshall error", err)
return nil, err
}

return obj, nil
}
41 changes: 41 additions & 0 deletions src/client/roomclient/model.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package roomclient

import "time"

type RoomDTO struct {
ID uint `json:"id"`
HostID uint `json:"hostID"`
Expand All @@ -11,6 +13,7 @@ type RoomDTO struct {
Photos []string `json:"photos"`
Commodities []string `json:"commodities"`
AutoApprove bool `json:"autoApprove"`
Deleted bool `json:"deleted"`
}

type CreateRoomDTO struct {
Expand All @@ -24,3 +27,41 @@ type CreateRoomDTO struct {
Commodities []string `json:"commodities"`
AutoApprove bool `json:"autoApprove"`
}

// ---------------------------------------------------------------

type CreateRoomAvailabilityListDTO struct {
RoomID uint `json:"roomId"`
Items []CreateRoomAvailabilityItemDTO `json:"items"`
}

type CreateRoomAvailabilityItemDTO struct {
// ExistingID is either the ID of an RoomAvailabilityItem that already
// exists, or 0 if this is a new item. When 0, a new one will be created in
// the DB. When not 0, it will reuse the existing object.
ExistingID uint `json:"existingId"`
DateFrom time.Time `json:"dateFrom"`
DateTo time.Time `json:"dateTo"`
Available bool `json:"available"`
}

// ---------------------------------------------------------------

type CreateRoomPriceListDTO struct {
RoomID uint `json:"roomId"`
Items []CreateRoomPriceItemDTO `json:"items"`
BasePrice uint `json:"basePrice"`
PerGuest bool `json:"perGuest"`
}

type CreateRoomPriceItemDTO struct {
// ExistingID is either the ID of an RoomPriceItem that already
// exists, or 0 if this is a new item. When 0, a new one will be created in
// the DB. When not 0, it will reuse the existing object.
ExistingID uint `json:"existingId"`
DateFrom time.Time `json:"dateFrom"`
DateTo time.Time `json:"dateTo"`
Price uint `json:"price"`
}

// ---------------------------------------------------------------
2 changes: 2 additions & 0 deletions src/domain/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type UserDTO struct {
Surname string `json:"surname" `
Address string `json:"address" `
Role string `json:"role" `
Deleted bool `json:"deleted" `
}

type UserUpdateDTO struct {
Expand All @@ -38,6 +39,7 @@ func NewUserDTO(user *User) UserDTO {
Surname: user.Surname,
Address: user.Address,
Role: string(user.Role),
Deleted: user.Deleted,
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/domain/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ var (
ErrDBInternal = errors.New("database internal error")
ErrInvalidInput = errors.New("invalid input")
ErrLoginFailed = errors.New("invalid user or password")
ErrDeletedAccount = errors.New("deleted account")
ErrUnauthorized = errors.New("unauthorized")
ErrPasswordsNotMatch = errors.New("confirm password does not match")
ErrPasswordNotChanged = errors.New("password must be different")
ErrUnauthenticated = errors.New("unauthenticated")
ErrNotFound = errors.New("not found")
ErrWrongPassword = errors.New("incorrect password")

ErrGuestHasReservations = errors.New("user has pending reservations")
ErrHostHasReservations = errors.New("user has room(s) with pending reservations")
ErrGuestHasReservations = errors.New("user has active reservations")
ErrHostHasReservations = errors.New("user has room(s) with active reservations")
ErrCannotDeleteAdmin = errors.New("admin accounts cannot be deleted")
)

Expand Down
1 change: 1 addition & 0 deletions src/domain/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ type User struct {
Surname string `json:"surname" gorm:"type:varchar(60);not null;"`
Role UserRole `json:"role" gorm:"type:varchar(5);not null;check:role IN ('guest','host','admin')"`
Address string `json:"address" gorm:"type:varchar(150);not null"`
Deleted bool `json:"deleted" gorm:"type:boolean;not null;default:false"`
}
7 changes: 4 additions & 3 deletions src/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type Repository interface {
FindByUsernameOrEmailNotId(username, email string, id uint) (*domain.User, error)
FindById(id uint) (*domain.User, error)
Update(user *domain.User) error
Delete(id uint)
Delete(user *domain.User) error
}

type repository struct {
Expand Down Expand Up @@ -58,6 +58,7 @@ func (r *repository) Update(user *domain.User) error {
return r.db.Save(user).Error
}

func (r *repository) Delete(id uint) {
r.db.Delete(&domain.User{}, id)
func (r *repository) Delete(user *domain.User) error {
user.Deleted = true
return r.Update(user)
}
19 changes: 18 additions & 1 deletion src/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ func (s *service) Login(ctx context.Context, dto domain.LoginDTO) (string, error
return "", domain.ErrLoginFailed
}

util.TEL.Push(ctx, "check-account-deletion")
defer util.TEL.Pop()
if user.Deleted {
util.TEL.Error("account is deleted", nil, "username_or_email", dto.UsernameOrEmail)
return "", domain.ErrDeletedAccount
}

util.TEL.Push(ctx, "verify-password")
defer util.TEL.Pop()

Expand Down Expand Up @@ -315,12 +322,22 @@ func (s *service) Delete(ctx context.Context, userId uint, jwt string) error {
return err
}

// Delete rooms

if user.Role == domain.Host {
_, err := s.roomClient.DeleteHostRooms(ctx, jwt)
if err != nil {
util.TEL.Error("could not delete host rooms", err)
return err
}
}

// Delete user

util.TEL.Push(ctx, "delete-user-in-db")
defer util.TEL.Pop()

s.repo.Delete(user.ID)
s.repo.Delete(user)
util.TEL.Info("User deleted", "id", userId)

return nil
Expand Down
Loading
Loading