Skip to content
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
16ff6dc
added NewInterview service function and converted funcs to methods
MichaelBoegner Oct 4, 2025
b12e42e
fixed integration and unit tests
MichaelBoegner Oct 4, 2025
7761284
added logger to new Interview service and service_test
MichaelBoegner Oct 5, 2025
4c40218
removed unnecessary function
MichaelBoegner Oct 5, 2025
73d3baf
converted user package services to methods
MichaelBoegner Oct 6, 2025
20673ef
added user service instantiation to server.go
MichaelBoegner Oct 7, 2025
7ab98fa
fixed handlers_test to use new user service
MichaelBoegner Oct 7, 2025
2838925
fixed service_test.go with NewUserService and logger instantiations
MichaelBoegner Oct 8, 2025
68b4309
replaced user. calls with h.UserService calls
MichaelBoegner Oct 8, 2025
2abbdf1
fixed billing to use BillingService for BillingRepo and UserRepo
MichaelBoegner Oct 8, 2025
d0f6c98
fixed billing.service_test.go
MichaelBoegner Oct 8, 2025
caa9c85
fixed test server billing.newBilling to include billingRepo and userRepo
MichaelBoegner Oct 8, 2025
9666c88
converted token service to TokenService struct and methods
MichaelBoegner Oct 8, 2025
c18795a
converted token service to TokenService struct with methods
MichaelBoegner Oct 8, 2025
567d69d
added TokenService to handlers and handlers_test
MichaelBoegner Oct 9, 2025
bcde338
extracted token.CreateJWT from user service to be orchestrated in han…
MichaelBoegner Oct 11, 2025
122bfb5
aligned OpenAI service with service naming being used in rest of serv…
MichaelBoegner Oct 12, 2025
839f360
further propegation of naming conversion as well as chaning OpenAISer…
MichaelBoegner Oct 12, 2025
3da4b89
further changes to explicit naming of services
MichaelBoegner Oct 13, 2025
8480db0
further clean up for billing services in handlers
MichaelBoegner Oct 13, 2025
3e05661
further cleanup in handlers for billing service naming
MichaelBoegner Oct 13, 2025
df12fc9
converted dashboard service to struct and methods
MichaelBoegner Oct 13, 2025
17a3883
started tackling conversation service conversion to struct and methods
MichaelBoegner Oct 13, 2025
fd90ac4
cleaned up conversation naming in handlers
MichaelBoegner Oct 13, 2025
3d10acc
cleaned up tests
MichaelBoegner Oct 13, 2025
70e01ef
conversation converted to service struct and methods
MichaelBoegner Oct 14, 2025
997414b
fixed conversation tests
MichaelBoegner Oct 14, 2025
9b71689
fixed billing service tests
MichaelBoegner Oct 14, 2025
78f8476
fixing user service test
MichaelBoegner Oct 14, 2025
c4a0eb0
fixed TestLoginUser unit test
MichaelBoegner Oct 16, 2025
dd9b247
resolved merge conflicts with main
MichaelBoegner Oct 16, 2025
dbd06fc
fixing copilot suggestions
MichaelBoegner Oct 17, 2025
bc2aa29
converted deductAndLogCredit and canUseCredit in interview service to…
MichaelBoegner Oct 20, 2025
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
12 changes: 9 additions & 3 deletions billing/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import (
"os"
"strconv"
"time"

"github.com/michaelboegner/interviewer/user"
)

type Billing struct {
type BillingService struct {
BillingRepo BillingRepo
UserRepo user.UserRepo
APIKey string
VariantIDIndividual int
VariantIDPro int
Expand Down Expand Up @@ -102,7 +106,7 @@ type BillingRepo interface {
MarkWebhookProcessed(id string, event string) error
}

func NewBilling(logger *slog.Logger) (*Billing, error) {
func NewBillingService(billingRepo BillingRepo, userRepo user.UserRepo, logger *slog.Logger) (*BillingService, error) {
individualID, err := strconv.Atoi(os.Getenv("LEMON_VARIANT_ID_INDIVIDUAL"))
if err != nil {
return nil, fmt.Errorf("invalid INDIVIDUAL ID: %w", err)
Expand All @@ -115,7 +119,9 @@ func NewBilling(logger *slog.Logger) (*Billing, error) {
if err != nil {
return nil, fmt.Errorf("invalid PREMIUM ID: %w", err)
}
return &Billing{
return &BillingService{
BillingRepo: billingRepo,
UserRepo: userRepo,
APIKey: os.Getenv("LEMON_API_KEY"),
VariantIDIndividual: individualID,
VariantIDPro: proID,
Expand Down
78 changes: 38 additions & 40 deletions billing/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ import (
"os"
"strconv"
"time"

"github.com/michaelboegner/interviewer/user"
)

func (b *Billing) RequestCheckoutSession(userEmail string, variantID int) (string, error) {
func (b *BillingService) RequestCheckoutSession(userEmail string, variantID int) (string, error) {
payload := CheckoutPayload{
Data: CheckoutData{
Type: "checkouts",
Expand Down Expand Up @@ -83,7 +81,7 @@ func (b *Billing) RequestCheckoutSession(userEmail string, variantID int) (strin
return result.Data.Attributes.URL, nil
}

func (b *Billing) RequestDeleteSubscription(subscriptionID string) error {
func (b *BillingService) RequestDeleteSubscription(subscriptionID string) error {
client := &http.Client{Timeout: 10 * time.Second}

req, err := http.NewRequest("DELETE", "https://api.lemonsqueezy.com/v1/subscriptions/"+subscriptionID, nil)
Expand All @@ -108,7 +106,7 @@ func (b *Billing) RequestDeleteSubscription(subscriptionID string) error {
return nil
}

func (b *Billing) RequestResumeSubscription(subscriptionID string) error {
func (b *BillingService) RequestResumeSubscription(subscriptionID string) error {
client := &http.Client{Timeout: 10 * time.Second}

payload := map[string]interface{}{
Expand Down Expand Up @@ -148,7 +146,7 @@ func (b *Billing) RequestResumeSubscription(subscriptionID string) error {
return nil
}

func (b *Billing) RequestUpdateSubscriptionVariant(subscriptionID string, newVariantID int) error {
func (b *BillingService) RequestUpdateSubscriptionVariant(subscriptionID string, newVariantID int) error {
payload := map[string]interface{}{
"data": map[string]interface{}{
"type": "subscriptions",
Expand Down Expand Up @@ -179,15 +177,15 @@ func (b *Billing) RequestUpdateSubscriptionVariant(subscriptionID string, newVar
return nil
}

func (b *Billing) VerifyBillingSignature(signature string, body []byte, secret string) bool {
func (b *BillingService) VerifyBillingSignature(signature string, body []byte, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}

func (b *Billing) ApplyCredits(userRepo user.UserRepo, billingRepo BillingRepo, email string, variantID int) error {
user, err := userRepo.GetUserByEmail(email)
func (b *BillingService) ApplyCredits(email string, variantID int) error {
user, err := b.UserRepo.GetUserByEmail(email)
if err != nil {
b.Logger.Error("repo.GetUserByEmail failed", "error", err)
return err
Expand Down Expand Up @@ -216,7 +214,7 @@ func (b *Billing) ApplyCredits(userRepo user.UserRepo, billingRepo BillingRepo,
return fmt.Errorf("unknown variant ID: %d", variantID)
}

if err := userRepo.AddCredits(user.ID, credits, creditType); err != nil {
if err := b.UserRepo.AddCredits(user.ID, credits, creditType); err != nil {
b.Logger.Error("repo.AddCredits failed", "error", err)
return err
}
Expand All @@ -227,16 +225,16 @@ func (b *Billing) ApplyCredits(userRepo user.UserRepo, billingRepo BillingRepo,
CreditType: creditType,
Reason: reason,
}
if err := billingRepo.LogCreditTransaction(tx); err != nil {
if err := b.BillingRepo.LogCreditTransaction(tx); err != nil {
b.Logger.Error("Warning: credit granted but failed to log transaction", "error", err)
return err
}

return nil
}

func (b *Billing) DeductCredits(userRepo user.UserRepo, billingRepo BillingRepo, orderAttrs OrderAttributes) error {
user, err := userRepo.GetUserByEmail(orderAttrs.UserEmail)
func (b *BillingService) DeductCredits(orderAttrs OrderAttributes) error {
user, err := b.UserRepo.GetUserByEmail(orderAttrs.UserEmail)
if err != nil {
b.Logger.Error("repo.GetUserByEmail failed", "error", err)
return err
Expand Down Expand Up @@ -267,7 +265,7 @@ func (b *Billing) DeductCredits(userRepo user.UserRepo, billingRepo BillingRepo,
return fmt.Errorf("unknown variant ID: %d", variantID)
}

if err := userRepo.AddCredits(user.ID, -credits, creditType); err != nil {
if err := b.UserRepo.AddCredits(user.ID, -credits, creditType); err != nil {
b.Logger.Error("repo.DeductCredits failed", "error", err)
return err
}
Expand All @@ -278,16 +276,16 @@ func (b *Billing) DeductCredits(userRepo user.UserRepo, billingRepo BillingRepo,
CreditType: creditType,
Reason: reason,
}
if err := billingRepo.LogCreditTransaction(tx); err != nil {
if err := b.BillingRepo.LogCreditTransaction(tx); err != nil {
b.Logger.Warn("Refund deduction succeeded but failed to log transaction", "error", err)
return err
}

return nil
}

func (b *Billing) CreateSubscription(userRepo user.UserRepo, subCreatedAttrs SubscriptionAttributes, subscriptionID string) error {
user, err := userRepo.GetUserByEmail(subCreatedAttrs.UserEmail)
func (b *BillingService) CreateSubscription(subCreatedAttrs SubscriptionAttributes, subscriptionID string) error {
user, err := b.UserRepo.GetUserByEmail(subCreatedAttrs.UserEmail)
if err != nil {
b.Logger.Error("repo.GetUserByEmail failed", "error", err)
return err
Expand All @@ -304,7 +302,7 @@ func (b *Billing) CreateSubscription(userRepo user.UserRepo, subCreatedAttrs Sub
return fmt.Errorf("unknown variant ID: %d", subCreatedAttrs.VariantID)
}

err = userRepo.UpdateSubscriptionData(
err = b.UserRepo.UpdateSubscriptionData(
user.ID,
"active",
tier,
Expand All @@ -320,14 +318,14 @@ func (b *Billing) CreateSubscription(userRepo user.UserRepo, subCreatedAttrs Sub
return nil
}

func (b *Billing) CancelSubscription(userRepo user.UserRepo, email string) error {
user, err := userRepo.GetUserByEmail(email)
func (b *BillingService) CancelSubscription(email string) error {
user, err := b.UserRepo.GetUserByEmail(email)
if err != nil {
b.Logger.Error("repo.GetUserByEmail failed", "error", err)
return err
}

err = userRepo.UpdateSubscriptionStatusData(
err = b.UserRepo.UpdateSubscriptionStatusData(
user.ID,
"cancelled",
)
Expand All @@ -339,14 +337,14 @@ func (b *Billing) CancelSubscription(userRepo user.UserRepo, email string) error
return nil
}

func (b *Billing) ResumeSubscription(userRepo user.UserRepo, email string) error {
user, err := userRepo.GetUserByEmail(email)
func (b *BillingService) ResumeSubscription(email string) error {
user, err := b.UserRepo.GetUserByEmail(email)
if err != nil {
b.Logger.Error("repo.GetUserByEmail failed", "error", err)
return err
}

err = userRepo.UpdateSubscriptionStatusData(
err = b.UserRepo.UpdateSubscriptionStatusData(
user.ID,
"active",
)
Expand All @@ -358,14 +356,14 @@ func (b *Billing) ResumeSubscription(userRepo user.UserRepo, email string) error
return nil
}

func (b *Billing) ExpireSubscription(userRepo user.UserRepo, billingRepo BillingRepo, email string) error {
user, err := userRepo.GetUserByEmail(email)
func (b *BillingService) ExpireSubscription(email string) error {
user, err := b.UserRepo.GetUserByEmail(email)
if err != nil {
b.Logger.Error("repo.GetUserByEmail failed", "error", err)
return err
}

err = userRepo.UpdateSubscriptionStatusData(
err = b.UserRepo.UpdateSubscriptionStatusData(
user.ID,
"expired",
)
Expand All @@ -375,7 +373,7 @@ func (b *Billing) ExpireSubscription(userRepo user.UserRepo, billingRepo Billing
}

if user.SubscriptionCredits > 0 {
err = userRepo.AddCredits(user.ID, -user.SubscriptionCredits, "subscription")
err = b.UserRepo.AddCredits(user.ID, -user.SubscriptionCredits, "subscription")
if err != nil {
b.Logger.Error("repo.AddCredits failed", "error", err)
return err
Expand All @@ -387,16 +385,16 @@ func (b *Billing) ExpireSubscription(userRepo user.UserRepo, billingRepo Billing
CreditType: "subscription",
Reason: "Zeroed out credits on subscription expiration",
}
if err := billingRepo.LogCreditTransaction(tx); err != nil {
if err := b.BillingRepo.LogCreditTransaction(tx); err != nil {
b.Logger.Warn("Zero-out succeeded but failed to log transaction", "error", err)
}
}

return nil
}

func (b *Billing) RenewSubscription(userRepo user.UserRepo, billingRepo BillingRepo, subRenewAttrs SubscriptionRenewAttributes) error {
user, err := userRepo.GetUserByEmail(subRenewAttrs.UserEmail)
func (b *BillingService) RenewSubscription(subRenewAttrs SubscriptionRenewAttributes) error {
user, err := b.UserRepo.GetUserByEmail(subRenewAttrs.UserEmail)
if err != nil {
b.Logger.Error("repo.GetUserByEmail failed", "error", err)
return err
Expand All @@ -422,7 +420,7 @@ func (b *Billing) RenewSubscription(userRepo user.UserRepo, billingRepo BillingR
return fmt.Errorf("unknown user.SubscriptionTier: %s", user.SubscriptionTier)
}

if err := userRepo.AddCredits(user.ID, credits, "subscription"); err != nil {
if err := b.UserRepo.AddCredits(user.ID, credits, "subscription"); err != nil {
b.Logger.Error("repo.AddCredits failed", "error", err)
return err
}
Expand All @@ -433,16 +431,16 @@ func (b *Billing) RenewSubscription(userRepo user.UserRepo, billingRepo BillingR
CreditType: "subscription",
Reason: reason,
}
if err := billingRepo.LogCreditTransaction(tx); err != nil {
if err := b.BillingRepo.LogCreditTransaction(tx); err != nil {
b.Logger.Warn("credit granted but failed to log transaction", "error", err)
return err
}

return nil
}

func (b *Billing) ChangeSubscription(userRepo user.UserRepo, billingRepo BillingRepo, subChangedAttrs SubscriptionAttributes) error {
user, err := userRepo.GetUserByEmail(subChangedAttrs.UserEmail)
func (b *BillingService) ChangeSubscription(subChangedAttrs SubscriptionAttributes) error {
user, err := b.UserRepo.GetUserByEmail(subChangedAttrs.UserEmail)
if err != nil {
b.Logger.Error("repo.GetUserByEmail failed", "error", err)
return err
Expand Down Expand Up @@ -471,7 +469,7 @@ func (b *Billing) ChangeSubscription(userRepo user.UserRepo, billingRepo Billing
return fmt.Errorf("unknown user.SubscriptionTier: %s", user.SubscriptionTier)
}

if err := userRepo.AddCredits(user.ID, credits, "subscription"); err != nil {
if err := b.UserRepo.AddCredits(user.ID, credits, "subscription"); err != nil {
b.Logger.Error("repo.AddCredits failed", "error", err)
return err
}
Expand All @@ -482,16 +480,16 @@ func (b *Billing) ChangeSubscription(userRepo user.UserRepo, billingRepo Billing
CreditType: "subscription",
Reason: reason,
}
if err := billingRepo.LogCreditTransaction(tx); err != nil {
if err := b.BillingRepo.LogCreditTransaction(tx); err != nil {
b.Logger.Warn("credit granted but failed to log transaction", "error", err)
return err
}

return nil
}

func (b *Billing) UpdateSubscription(userRepo user.UserRepo, subUpdatedAttrs SubscriptionAttributes, subscriptionID string) error {
user, err := userRepo.GetUserByEmail(subUpdatedAttrs.UserEmail)
func (b *BillingService) UpdateSubscription(subUpdatedAttrs SubscriptionAttributes, subscriptionID string) error {
user, err := b.UserRepo.GetUserByEmail(subUpdatedAttrs.UserEmail)
if err != nil {
b.Logger.Error("repo.GetUserByEmail failed", "error", err)
return err
Expand All @@ -508,7 +506,7 @@ func (b *Billing) UpdateSubscription(userRepo user.UserRepo, subUpdatedAttrs Sub
return fmt.Errorf("unknown variant ID: %d", subUpdatedAttrs.VariantID)
}

err = userRepo.UpdateSubscriptionData(
err = b.UserRepo.UpdateSubscriptionData(
user.ID,
subUpdatedAttrs.Status,
tier,
Expand Down
Loading