Skip to content

Commit ef0a035

Browse files
committed
Implement seat-based billing and logging enhancements. Introduce AdminLogsHandler for managing server logs, including listing and downloading logs. Update subscription management to support seat-based billing, ensuring proper validation and error handling. Enhance payment service to include seat quantity in checkout sessions and update subscription seats dynamically. Refactor logging mechanisms to separate application and HTTP logs, improving log management and traceability.
1 parent 350e24d commit ef0a035

File tree

17 files changed

+1269
-81
lines changed

17 files changed

+1269
-81
lines changed

cmd/passwall-server/main.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ import (
66
"log"
77
"os"
88
"os/signal"
9+
"strings"
910
"syscall"
1011

1112
"github.com/passwall/passwall-server/internal/core"
1213
"github.com/passwall/passwall-server/pkg/buildvars"
14+
"github.com/passwall/passwall-server/pkg/constants"
1315
"github.com/passwall/passwall-server/pkg/logger"
1416
)
1517

1618
func main() {
19+
applyWorkDir()
1720
logStartupInfo()
1821

1922
// Create application context with signal handling
@@ -46,6 +49,21 @@ func main() {
4649
fmt.Println("✅ Server stopped")
4750
}
4851

52+
func applyWorkDir() {
53+
workDir := strings.TrimSpace(os.Getenv(constants.WorkDirEnv))
54+
if workDir == "" {
55+
return
56+
}
57+
58+
if err := os.MkdirAll(workDir, 0755); err != nil {
59+
log.Fatalf("Failed to create work dir %q: %v", workDir, err)
60+
}
61+
62+
if err := os.Chdir(workDir); err != nil {
63+
log.Fatalf("Failed to change work dir to %q: %v", workDir, err)
64+
}
65+
}
66+
4967
func logStartupInfo() {
5068
args := os.Args
5169
if args == nil {

internal/core/app.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ func (a *App) Run(ctx context.Context) error {
239239
serviceLogger,
240240
)
241241
adminMailHandler := httpHandler.NewAdminMailHandler(emailSender, userRepo, serviceLogger)
242+
adminLogsHandler := httpHandler.NewAdminLogsHandler()
242243

243244
// Setup router
244245
router := SetupRouter(
@@ -266,6 +267,7 @@ func (a *App) Run(ctx context.Context) error {
266267
plansHandler,
267268
adminSubscriptionsHandler,
268269
adminMailHandler,
270+
adminLogsHandler,
269271
)
270272

271273
// Create server

internal/core/router.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func SetupRouter(
3737
plansHandler *httpHandler.PlansHandler,
3838
adminSubscriptionsHandler *httpHandler.AdminSubscriptionsHandler,
3939
adminMailHandler *httpHandler.AdminMailHandler,
40+
adminLogsHandler *httpHandler.AdminLogsHandler,
4041
) *gin.Engine {
4142
// Create router without default middleware
4243
router := gin.New()
@@ -223,6 +224,10 @@ func SetupRouter(
223224
// Mail (admin broadcast)
224225
adminGroup.POST("/mail", adminMailHandler.CreateJob)
225226
adminGroup.GET("/mail/:jobId", adminMailHandler.GetJob)
227+
// Server logs (admin)
228+
adminGroup.GET("/logs", adminLogsHandler.List)
229+
adminGroup.GET("/logs/download", adminLogsHandler.Download)
230+
adminGroup.GET("/logs/download-bundle", adminLogsHandler.DownloadBundle)
226231

227232
// Legacy endpoint (backward compatibility)
228233
adminGroup.POST("/bulk-email", adminMailHandler.CreateJob)
@@ -262,6 +267,7 @@ func SetupRouter(
262267
// Payment & Billing routes
263268
orgsGroup.POST("/:id/checkout", paymentHandler.CreateCheckoutSession)
264269
orgsGroup.GET("/:id/billing", paymentHandler.GetBillingInfo)
270+
orgsGroup.POST("/:id/subscription/seats", paymentHandler.UpdateSubscriptionSeats)
265271
orgsGroup.POST("/:id/subscription/cancel", paymentHandler.CancelSubscription)
266272
orgsGroup.POST("/:id/subscription/reactivate", paymentHandler.ReactivateSubscription)
267273
orgsGroup.POST("/:id/subscription/sync", paymentHandler.SyncSubscription)

internal/domain/subscription.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ type Subscription struct {
4545
// Stripe integration
4646
StripeSubscriptionID *string `json:"stripe_subscription_id,omitempty" gorm:"type:varchar(255);uniqueIndex"`
4747

48+
// Seat-based billing (quantity-based Stripe subscriptions)
49+
// Null means "not seat-based / unknown". When set, it becomes the effective user limit.
50+
SeatsPurchased *int `json:"seats_purchased,omitempty" gorm:"index"`
51+
4852
// Associations
4953
Organization *Organization `json:"organization,omitempty" gorm:"foreignKey:OrganizationID"`
5054
Plan *Plan `json:"plan,omitempty" gorm:"foreignKey:PlanID"`
@@ -111,6 +115,7 @@ type SubscriptionDTO struct {
111115
GracePeriodEndsAt *time.Time `json:"grace_period_ends_at,omitempty"`
112116
TrialEndsAt *time.Time `json:"trial_ends_at,omitempty"`
113117
StripeSubscriptionID *string `json:"stripe_subscription_id,omitempty"`
118+
SeatsPurchased *int `json:"seats_purchased,omitempty"`
114119
CreatedAt time.Time `json:"created_at"`
115120
UpdatedAt time.Time `json:"updated_at"`
116121
}
@@ -148,6 +153,7 @@ func ToSubscriptionDTO(s *Subscription) *SubscriptionDTO {
148153
GracePeriodEndsAt: s.GracePeriodEndsAt,
149154
TrialEndsAt: s.TrialEndsAt,
150155
StripeSubscriptionID: s.StripeSubscriptionID,
156+
SeatsPurchased: s.SeatsPurchased,
151157
CreatedAt: s.CreatedAt,
152158
UpdatedAt: s.UpdatedAt,
153159
}

0 commit comments

Comments
 (0)