package entity
import (
"errors"
"time"
)
const (
StatusCreated = "created"
StatusPending = "pending"
StatusDeclined = "declined"
StatusApproved = "approved"
)
type Booking struct {
id string
status string
statusReason string
firstName string
lastName string
gender string
birthday time.Time
launchpadID string
destinationID string
launchDate time.Time
}
func CreateBooking(firstName, lastName, gender string, birthday time.Time, launchpadID, destinationID string, launchDate time.Time) *Booking {
b := &Booking{
firstName: firstName, lastName: lastName, gender: gender, birthday: birthday,
launchpadID: launchpadID, destinationID: destinationID, launchDate: launchDate,
}
b.status = StatusCreated
return b
}
func LoadBooking(id, status, statusReason, firstName, lastName, gender string, birthday time.Time, launchpadID, destinationID string, launchDate time.Time) *Booking {
b := &Booking{
id: id, status: status, statusReason: statusReason, firstName: firstName, lastName: lastName, gender: gender,
birthday: birthday, launchpadID: launchpadID, destinationID: destinationID, launchDate: launchDate,
}
return b
}
func (b *Booking) ID() string {
return b.id
}
func (b *Booking) SetID(id string) {
b.id = id
}
func (b *Booking) Status() string {
return b.status
}
func (b *Booking) SetStatus(status string) {
b.status = status
}
func (b *Booking) StatusReason() string {
return b.statusReason
}
func (b *Booking) SetStatusReason(statusReason string) {
b.statusReason = statusReason
}
func (b *Booking) FirstName() string {
return b.firstName
}
func (b *Booking) SetFirstName(firstName string) {
b.firstName = firstName
}
func (b *Booking) LastName() string {
return b.lastName
}
func (b *Booking) SetLastName(lastName string) {
b.lastName = lastName
}
func (b *Booking) Gender() string {
return b.gender
}
func (b *Booking) SetGender(gender string) {
b.gender = gender
}
func (b *Booking) Birthday() time.Time {
return b.birthday
}
func (b *Booking) SetBirthday(birthday time.Time) {
b.birthday = birthday
}
func (b *Booking) LaunchpadID() string {
return b.launchpadID
}
func (b *Booking) SetLaunchpadID(launchpadID string) {
b.launchpadID = launchpadID
}
func (b *Booking) DestinationID() string {
return b.destinationID
}
func (b *Booking) SetDestinationID(destinationID string) {
b.destinationID = destinationID
}
func (b *Booking) LaunchDate() time.Time {
return b.launchDate
}
func (b *Booking) SetLaunchDate(launchDate time.Time) {
b.launchDate = launchDate
}
func (b Booking) Validate() error {
if err := b.validateStatus(); err != nil {
return err
}
if err := b.validateFirstName(); err != nil {
return err
}
if err := b.validateLastName(); err != nil {
return err
}
if err := b.validateGender(); err != nil {
return err
}
if err := b.validateBirthday(); err != nil {
return err
}
if err := b.validateLaunchpadID(); err != nil {
return err
}
if err := b.validateDestinationID(); err != nil {
return err
}
if err := b.validateLaunchDate(); err != nil {
return err
}
return nil
}
func (b Booking) validateStatus() error {
if b.Status() == "" {
return errors.New("value of \"status\" field can't be empty")
}
return nil
}
func (b Booking) validateFirstName() error {
if b.FirstName() == "" {
return errors.New("value of \"firstName\" field can't be empty")
}
return nil
}
func (b Booking) validateLastName() error {
if b.LastName() == "" {
return errors.New("value of \"lastName\" field can't be empty")
}
return nil
}
func (b Booking) validateGender() error {
if b.Gender() == "" {
return errors.New("value of \"gender\" field can't be empty")
}
return nil
}
func (b Booking) validateBirthday() error {
if b.Birthday().After(time.Now()) {
// actually should be more than at least 18 years
return errors.New("value of \"birthday\" field should be in the past")
}
return nil
}
func (b Booking) validateLaunchpadID() error {
if b.LaunchpadID() == "" {
return errors.New("value of \"launchpadID\" field can't be empty")
}
return nil
}
func (b Booking) validateDestinationID() error {
if b.DestinationID() == "" {
return errors.New("value of \"destinationID\" field can't be empty")
}
return nil
}
func (b Booking) validateLaunchDate() error {
if b.LaunchDate().Before(time.Now()) {
return errors.New("value of \"launchDate\" field should be in the future")
}
return nil
}
package service
import (
"github.com/mgerasimchuk/space-trouble/internal/entity"
"github.com/mgerasimchuk/space-trouble/internal/entity/service/dto"
"github.com/pkg/errors"
)
type BookingVerifierService struct {
}
func NewBookingVerifierService() *BookingVerifierService {
return &BookingVerifierService{}
}
func (s BookingVerifierService) Verify(b *entity.Booking, dto dto.BookingVerifyDTO) error {
if !dto.IsLaunchpadExists {
b.SetStatus(entity.StatusDeclined)
err := errors.New("launchpad doesn't exists")
b.SetStatusReason(err.Error())
return err
}
if !dto.IsLaunchpadActive {
b.SetStatus(entity.StatusDeclined)
err := errors.New("launchpad is not active")
b.SetStatusReason(err.Error())
return err
}
if !dto.IsDateAvailableForLaunch {
b.SetStatus(entity.StatusDeclined)
err := errors.New("booking date is not available")
b.SetStatusReason(err.Error())
return err
}
if !dto.IsLandpadExists {
b.SetStatus(entity.StatusDeclined)
err := errors.New("landpad doesn't exists")
b.SetStatusReason(err.Error())
return err
}
if !dto.IsLandpadActive {
b.SetStatus(entity.StatusDeclined)
err := errors.New("landpad is not active")
b.SetStatusReason(err.Error())
return err
}
b.SetStatus(entity.StatusApproved)
b.SetStatusReason("")
return nil
}
package usecase
import (
"errors"
"github.com/mgerasimchuk/space-trouble/internal/entity"
"github.com/mgerasimchuk/space-trouble/internal/entity/service"
"github.com/mgerasimchuk/space-trouble/internal/entity/service/dto"
"github.com/mgerasimchuk/space-trouble/internal/usecase/repository"
"golang.org/x/sync/errgroup"
"time"
)
type BookingUsecase struct {
bookingVerifierSvc *service.BookingVerifierService
bookingRepo repository.BookingRepository
launchpadRepo repository.LaunchpadRepository
landpadRepo repository.LandpadRepository
}
func NewBookingUsecase(bookingSvc *service.BookingVerifierService, bookingRepo repository.BookingRepository, launchpadRepo repository.LaunchpadRepository, landpadRepo repository.LandpadRepository) *BookingUsecase {
return &BookingUsecase{bookingVerifierSvc: bookingSvc, bookingRepo: bookingRepo, launchpadRepo: launchpadRepo, landpadRepo: landpadRepo}
}
var internalError = errors.New("internal error")
func (u *BookingUsecase) CreateBooking(
firstName string, lastName string, gender string, birthday time.Time,
launchpadID string, destinationID string, launchDate time.Time,
) (*entity.Booking, error) {
b := entity.CreateBooking(firstName, lastName, gender, birthday, launchpadID, destinationID, launchDate)
if err := b.Validate(); err != nil {
return nil, err
}
b, err := u.bookingRepo.Create(b)
if err != nil {
return nil, internalError
}
return b, nil
}
func (u *BookingUsecase) GetBookings(limit, offset *int) ([]*entity.Booking, error) {
if limit != nil && *limit < 1 {
return nil, errors.New("\"limit\" param should be greater than 1")
}
if offset != nil && *offset < 0 {
return nil, errors.New("\"offset\" param should be greater or equal 0")
}
bookings, err := u.bookingRepo.GetList(nil, limit, offset)
if err != nil {
return nil, internalError
}
return bookings, nil
}
func (u *BookingUsecase) DeleteBooking(id string) error {
if id == "" {
return errors.New("\"id\" can't be empty")
}
err := u.bookingRepo.Delete(id)
if err != nil {
return internalError
}
return nil
}
func (u *BookingUsecase) VerifyFirstAvailableBooking() error {
b, err := u.bookingRepo.GetAndModify(entity.StatusCreated, entity.StatusPending)
if err != nil {
return err
}
if b == nil {
return nil
}
bookingVerifyDTO := dto.BookingVerifyDTO{}
g := errgroup.Group{}
g.Go(func() (err error) {
bookingVerifyDTO.IsLaunchpadExists, err = u.launchpadRepo.IsExists(b.LaunchpadID())
return err
})
g.Go(func() (err error) {
bookingVerifyDTO.IsLaunchpadActive, err = u.launchpadRepo.IsActive(b.LaunchpadID())
return err
})
g.Go(func() (err error) {
bookingVerifyDTO.IsDateAvailableForLaunch, err = u.launchpadRepo.IsDateAvailableForLaunch(b.LaunchpadID(), b.LaunchDate())
return err
})
g.Go(func() (err error) {
bookingVerifyDTO.IsLandpadExists, err = u.landpadRepo.IsExists(b.DestinationID())
return err
})
g.Go(func() (err error) {
bookingVerifyDTO.IsLandpadActive, err = u.landpadRepo.IsActive(b.DestinationID())
return err
})
if err = g.Wait(); err != nil {
b.SetStatus(entity.StatusDeclined)
b.SetStatusReason("Unknown reason") // hide errors from the adapters layer
return err
}
err = u.bookingVerifierSvc.Verify(b, bookingVerifyDTO)
if err != nil {
return err
}
return u.bookingRepo.Save(b)
}