mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-06-03 20:13:00 +00:00
cleanup
This commit is contained in:
@@ -52,12 +52,15 @@ func (s *Service) Bootstrap(ctx context.Context, principal domain.Principal) (do
|
||||
}
|
||||
|
||||
return domain.TenantBootstrap{
|
||||
TenantID: membership.Tenant.ID,
|
||||
TenantName: membership.Tenant.Name,
|
||||
Preset: membership.Tenant.Preset,
|
||||
Locale: membership.Tenant.Locale,
|
||||
Timezone: membership.Tenant.Timezone,
|
||||
PlanCode: membership.Tenant.PlanCode,
|
||||
TenantID: membership.Tenant.ID,
|
||||
TenantName: membership.Tenant.Name,
|
||||
TenantSlug: membership.Tenant.Slug,
|
||||
Preset: membership.Tenant.Preset,
|
||||
Locale: membership.Tenant.Locale,
|
||||
Timezone: membership.Tenant.Timezone,
|
||||
PlanCode: normalizePlanCode(membership.Tenant.PlanCode),
|
||||
OnboardingCompleted: true,
|
||||
Brand: s.brandProfile(ctx, membership.Tenant),
|
||||
CurrentUser: domain.Principal{
|
||||
Subject: principal.Subject,
|
||||
Email: principal.Email,
|
||||
@@ -79,6 +82,21 @@ func (s *Service) Onboard(ctx context.Context, principal domain.Principal, reque
|
||||
preset := strings.TrimSpace(request.Preset)
|
||||
locale := strings.TrimSpace(request.Locale)
|
||||
timezone := strings.TrimSpace(request.Timezone)
|
||||
locationName := strings.TrimSpace(request.LocationName)
|
||||
brand := request.Brand
|
||||
if strings.TrimSpace(brand.Name) == "" {
|
||||
brand.Name = name
|
||||
}
|
||||
defaults := request.BookingDefaults
|
||||
if strings.TrimSpace(defaults.ServiceName) == "" {
|
||||
defaults.ServiceName = "First appointment"
|
||||
}
|
||||
if defaults.DurationMinutes == 0 {
|
||||
defaults.DurationMinutes = 60
|
||||
}
|
||||
if defaults.CancelWindowHours == 0 {
|
||||
defaults.CancelWindowHours = 24
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(name) < 2 || len(name) > 80:
|
||||
@@ -91,18 +109,43 @@ func (s *Service) Onboard(ctx context.Context, principal domain.Principal, reque
|
||||
return domain.TenantBootstrap{}, ErrInvalidOnboarding
|
||||
case timezone == "":
|
||||
return domain.TenantBootstrap{}, ErrInvalidOnboarding
|
||||
case len(locationName) > 120:
|
||||
return domain.TenantBootstrap{}, ErrInvalidOnboarding
|
||||
case defaults.DurationMinutes < 15 || defaults.DurationMinutes > 480:
|
||||
return domain.TenantBootstrap{}, ErrInvalidOnboarding
|
||||
case defaults.BufferBeforeMinutes < 0 || defaults.BufferBeforeMinutes > 180:
|
||||
return domain.TenantBootstrap{}, ErrInvalidOnboarding
|
||||
case defaults.BufferAfterMinutes < 0 || defaults.BufferAfterMinutes > 180:
|
||||
return domain.TenantBootstrap{}, ErrInvalidOnboarding
|
||||
case defaults.CancelWindowHours < 1 || defaults.CancelWindowHours > 720:
|
||||
return domain.TenantBootstrap{}, ErrInvalidOnboarding
|
||||
}
|
||||
if _, err := time.LoadLocation(timezone); err != nil {
|
||||
return domain.TenantBootstrap{}, ErrInvalidOnboarding
|
||||
}
|
||||
if err := validateAvailabilityBlocks(request.AvailabilityBlocks); err != nil {
|
||||
return domain.TenantBootstrap{}, err
|
||||
}
|
||||
|
||||
membership, err := s.repo.CreateTenantForUser(ctx, db.CreateTenantForUserParams{
|
||||
Subject: principal.Subject,
|
||||
Name: name,
|
||||
Slug: slug,
|
||||
Preset: preset,
|
||||
Locale: locale,
|
||||
Timezone: timezone,
|
||||
Subject: principal.Subject,
|
||||
Name: name,
|
||||
Slug: slug,
|
||||
Preset: preset,
|
||||
Locale: locale,
|
||||
Timezone: timezone,
|
||||
BrandName: strings.TrimSpace(brand.Name),
|
||||
SiteURL: strings.TrimSpace(brand.SiteURL),
|
||||
LogoURL: strings.TrimSpace(brand.LogoURL),
|
||||
PrimaryColor: strings.TrimSpace(brand.PrimaryColor),
|
||||
LocationName: locationName,
|
||||
ServiceName: strings.TrimSpace(defaults.ServiceName),
|
||||
DurationMinutes: defaults.DurationMinutes,
|
||||
BufferBeforeMinutes: defaults.BufferBeforeMinutes,
|
||||
BufferAfterMinutes: defaults.BufferAfterMinutes,
|
||||
CancelWindowHours: defaults.CancelWindowHours,
|
||||
AvailabilityBlocks: toAvailabilityBlocks(request.AvailabilityBlocks),
|
||||
TeamInvites: toTeamInvites(request.TeamInvites),
|
||||
})
|
||||
if err != nil {
|
||||
var pgErr *pgconn.PgError
|
||||
@@ -113,12 +156,20 @@ func (s *Service) Onboard(ctx context.Context, principal domain.Principal, reque
|
||||
}
|
||||
|
||||
return domain.TenantBootstrap{
|
||||
TenantID: membership.Tenant.ID,
|
||||
TenantName: membership.Tenant.Name,
|
||||
Preset: membership.Tenant.Preset,
|
||||
Locale: membership.Tenant.Locale,
|
||||
Timezone: membership.Tenant.Timezone,
|
||||
PlanCode: membership.Tenant.PlanCode,
|
||||
TenantID: membership.Tenant.ID,
|
||||
TenantName: membership.Tenant.Name,
|
||||
TenantSlug: membership.Tenant.Slug,
|
||||
Preset: membership.Tenant.Preset,
|
||||
Locale: membership.Tenant.Locale,
|
||||
Timezone: membership.Tenant.Timezone,
|
||||
PlanCode: normalizePlanCode(membership.Tenant.PlanCode),
|
||||
OnboardingCompleted: true,
|
||||
Brand: domain.BrandProfile{
|
||||
Name: strings.TrimSpace(brand.Name),
|
||||
SiteURL: strings.TrimSpace(brand.SiteURL),
|
||||
LogoURL: strings.TrimSpace(brand.LogoURL),
|
||||
PrimaryColor: strings.TrimSpace(brand.PrimaryColor),
|
||||
},
|
||||
CurrentUser: domain.Principal{
|
||||
Subject: principal.Subject,
|
||||
Email: principal.Email,
|
||||
@@ -127,3 +178,96 @@ func (s *Service) Onboard(ctx context.Context, principal domain.Principal, reque
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) brandProfile(ctx context.Context, tenant db.TenantRecord) domain.BrandProfile {
|
||||
brand, err := s.repo.GetBrandProfile(ctx, tenant.ID)
|
||||
if err != nil {
|
||||
return domain.BrandProfile{Name: tenant.Name}
|
||||
}
|
||||
return domain.BrandProfile{
|
||||
Name: firstNonEmpty(brand.Name, tenant.Name),
|
||||
SiteURL: brand.SiteURL,
|
||||
LogoURL: brand.LogoURL,
|
||||
PrimaryColor: brand.PrimaryColor,
|
||||
}
|
||||
}
|
||||
|
||||
func validateAvailabilityBlocks(blocks []domain.AvailabilityBlockRequest) error {
|
||||
for _, block := range blocks {
|
||||
if block.DayOfWeek < 0 || block.DayOfWeek > 6 {
|
||||
return ErrInvalidOnboarding
|
||||
}
|
||||
starts, err := time.Parse("15:04", block.StartsLocal)
|
||||
if err != nil {
|
||||
starts, err = time.Parse("15:04:05", block.StartsLocal)
|
||||
}
|
||||
if err != nil {
|
||||
return ErrInvalidOnboarding
|
||||
}
|
||||
ends, err := time.Parse("15:04", block.EndsLocal)
|
||||
if err != nil {
|
||||
ends, err = time.Parse("15:04:05", block.EndsLocal)
|
||||
}
|
||||
if err != nil || !ends.After(starts) {
|
||||
return ErrInvalidOnboarding
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toAvailabilityBlocks(blocks []domain.AvailabilityBlockRequest) []db.AvailabilityBlockRecord {
|
||||
records := make([]db.AvailabilityBlockRecord, 0, len(blocks))
|
||||
for _, block := range blocks {
|
||||
records = append(records, db.AvailabilityBlockRecord{
|
||||
DayOfWeek: block.DayOfWeek,
|
||||
StartsLocal: normalizeClock(block.StartsLocal),
|
||||
EndsLocal: normalizeClock(block.EndsLocal),
|
||||
Busy: block.Busy,
|
||||
})
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
func toTeamInvites(invites []domain.TeamInviteRequest) []db.TeamInviteRecord {
|
||||
records := make([]db.TeamInviteRecord, 0, len(invites))
|
||||
for _, invite := range invites {
|
||||
email := strings.TrimSpace(strings.ToLower(invite.Email))
|
||||
if email == "" {
|
||||
continue
|
||||
}
|
||||
role := strings.TrimSpace(invite.Role)
|
||||
if role == "" {
|
||||
role = "staff"
|
||||
}
|
||||
records = append(records, db.TeamInviteRecord{Email: email, Role: role})
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
func normalizeClock(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if len(value) == len("15:04") {
|
||||
return value + ":00"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func normalizePlanCode(planCode string) string {
|
||||
switch planCode {
|
||||
case "growth":
|
||||
return "pro"
|
||||
case "multi-location":
|
||||
return "business"
|
||||
default:
|
||||
return planCode
|
||||
}
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user