mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-06-03 20:13:00 +00:00
feat(core): consolidate auth service into backend and implement stripe billing
This commit performs a major architectural refactor by migrating the standalone `auth-service` into the main `backend` application, enabling a unified codebase and simplified deployment. It also introduces comprehensive Stripe billing support and a new administrative dashboard.
Key changes:
- **Architecture**: Deleted `apps/auth-service` and integrated its functionality (JWT, magic links, OAuth, user management) into `apps/backend`.
- **Billing**: Added Stripe integration to `backend`, supporting both monthly and yearly subscription cycles with automatic plan entitlement enforcement (e.g., location limits).
- **Admin Dashboard**: Implemented a new administrative service and API endpoints to manage tenants, users, and view platform-wide statistics.
- **Frontend**:
- Added a new pricing page with monthly/yearly toggle and comparison table.
- Integrated Stripe and Sentry for payments and error tracking.
- Improved dashboard UX/UI and added i18n support for new features.
- Enhanced the public booking flow with better validation and contact form integration.
- **Database**: Added migrations for users, magic links, password resets, OAuth states, admin audit logs, and refresh tokens.
- **DevOps**: Updated environment configurations for Railway and Vercel, and streamlined the project's `package.json` scripts.
This commit is contained in:
@@ -3,6 +3,7 @@ package catalog
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"bookra/apps/backend/internal/db"
|
||||
@@ -17,14 +18,25 @@ var (
|
||||
ErrInvalidBooking = errors.New("invalid booking request")
|
||||
ErrTenantNotFound = errors.New("tenant not found")
|
||||
ErrTenantMembership = errors.New("tenant membership not found")
|
||||
ErrPlanLimitReached = errors.New("plan limit reached")
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
repo db.Repository
|
||||
repo db.Repository
|
||||
billingService interface {
|
||||
GetEntitlements(ctx context.Context, tenantID string) (domain.PlanEntitlements, error)
|
||||
}
|
||||
notificationService interface {
|
||||
SendUsageWarning(ctx context.Context, tenantID string, locationCount, locationLimit, usagePercent int) error
|
||||
}
|
||||
}
|
||||
|
||||
func NewService(repo db.Repository) *Service {
|
||||
return &Service{repo: repo}
|
||||
func NewService(repo db.Repository, billingService interface {
|
||||
GetEntitlements(ctx context.Context, tenantID string) (domain.PlanEntitlements, error)
|
||||
}, notificationService interface {
|
||||
SendUsageWarning(ctx context.Context, tenantID string, locationCount, locationLimit, usagePercent int) error
|
||||
}) *Service {
|
||||
return &Service{repo: repo, billingService: billingService, notificationService: notificationService}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
@@ -63,6 +75,18 @@ func (s *Service) CreateLocation(ctx context.Context, principal domain.Principal
|
||||
return domain.Location{}, ErrTenantMembership
|
||||
}
|
||||
|
||||
// Check plan entitlements for location limit
|
||||
if s.billingService != nil {
|
||||
entitlements, err := s.billingService.GetEntitlements(ctx, membership.Tenant.ID)
|
||||
if err == nil && entitlements.MaxLocations > 0 {
|
||||
// Count existing locations
|
||||
locations, err := s.repo.ListLocationsByTenant(ctx, membership.Tenant.ID)
|
||||
if err == nil && len(locations) >= entitlements.MaxLocations {
|
||||
return domain.Location{}, fmt.Errorf("%w: location limit reached (%d/%d). Upgrade your plan to add more locations.", ErrPlanLimitReached, len(locations), entitlements.MaxLocations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params := db.CreateLocationParams{
|
||||
TenantID: membership.Tenant.ID,
|
||||
Name: req.Name,
|
||||
@@ -74,6 +98,18 @@ func (s *Service) CreateLocation(ctx context.Context, principal domain.Principal
|
||||
return domain.Location{}, err
|
||||
}
|
||||
|
||||
// Send usage warning if at 80%+ of limit
|
||||
if s.notificationService != nil && s.billingService != nil {
|
||||
entitlements, err := s.billingService.GetEntitlements(ctx, membership.Tenant.ID)
|
||||
if err == nil && entitlements.MaxLocations > 0 {
|
||||
locations, _ := s.repo.ListLocationsByTenant(ctx, membership.Tenant.ID)
|
||||
usagePercent := (len(locations) * 100) / entitlements.MaxLocations
|
||||
if usagePercent >= 80 {
|
||||
_ = s.notificationService.SendUsageWarning(ctx, membership.Tenant.ID, len(locations), entitlements.MaxLocations, usagePercent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return domain.Location{
|
||||
ID: rec.ID,
|
||||
TenantID: rec.TenantID,
|
||||
|
||||
Reference in New Issue
Block a user