Add Blossom blob storage server and subscription management

- Introduced the `initializeBlossomServer` function to set up the Blossom blob storage server with dynamic base URL handling and ACL configuration.
- Implemented the `blossomHandler` method to manage incoming requests to the Blossom API, ensuring proper URL handling and context management.
- Enhanced the `PaymentProcessor` to support Blossom service levels, allowing for subscription extensions based on payment metadata.
- Added methods for parsing and validating Blossom service levels, including storage quota management and subscription extension logic.
- Updated the configuration to include Blossom service level settings, facilitating dynamic service level management.
- Integrated storage quota checks in the blob upload process to prevent exceeding allocated limits.
- Refactored existing code to improve organization and maintainability, including the removal of unused blob directory configurations.
- Added tests to ensure the robustness of new functionalities and maintain existing behavior across blob operations.
This commit is contained in:
2025-11-02 22:23:01 +00:00
parent 3567bb26a4
commit edcdec9c7e
10 changed files with 421 additions and 24 deletions

View File

@@ -13,8 +13,10 @@ import (
)
type Subscription struct {
TrialEnd time.Time `json:"trial_end"`
PaidUntil time.Time `json:"paid_until"`
TrialEnd time.Time `json:"trial_end"`
PaidUntil time.Time `json:"paid_until"`
BlossomLevel string `json:"blossom_level,omitempty"` // Service level name (e.g., "basic", "premium")
BlossomStorage int64 `json:"blossom_storage,omitempty"` // Storage quota in MB
}
func (d *D) GetSubscription(pubkey []byte) (*Subscription, error) {
@@ -190,6 +192,77 @@ func (d *D) GetPaymentHistory(pubkey []byte) ([]Payment, error) {
return payments, err
}
// ExtendBlossomSubscription extends or creates a blossom subscription with service level
func (d *D) ExtendBlossomSubscription(
pubkey []byte, level string, storageMB int64, days int,
) error {
if days <= 0 {
return fmt.Errorf("invalid days: %d", days)
}
key := fmt.Sprintf("sub:%s", hex.EncodeToString(pubkey))
now := time.Now()
return d.DB.Update(
func(txn *badger.Txn) error {
var sub Subscription
item, err := txn.Get([]byte(key))
if errors.Is(err, badger.ErrKeyNotFound) {
sub.PaidUntil = now.AddDate(0, 0, days)
} else if err != nil {
return err
} else {
err = item.Value(
func(val []byte) error {
return json.Unmarshal(val, &sub)
},
)
if err != nil {
return err
}
extendFrom := now
if !sub.PaidUntil.IsZero() && sub.PaidUntil.After(now) {
extendFrom = sub.PaidUntil
}
sub.PaidUntil = extendFrom.AddDate(0, 0, days)
}
// Set blossom service level and storage
sub.BlossomLevel = level
// Add storage quota (accumulate if subscription already exists)
if sub.BlossomStorage > 0 && sub.PaidUntil.After(now) {
// Add to existing quota
sub.BlossomStorage += storageMB
} else {
// Set new quota
sub.BlossomStorage = storageMB
}
data, err := json.Marshal(&sub)
if err != nil {
return err
}
return txn.Set([]byte(key), data)
},
)
}
// GetBlossomStorageQuota returns the current blossom storage quota in MB for a pubkey
func (d *D) GetBlossomStorageQuota(pubkey []byte) (quotaMB int64, err error) {
sub, err := d.GetSubscription(pubkey)
if err != nil {
return 0, err
}
if sub == nil {
return 0, nil
}
// Only return quota if subscription is active
if sub.PaidUntil.IsZero() || time.Now().After(sub.PaidUntil) {
return 0, nil
}
return sub.BlossomStorage, nil
}
// IsFirstTimeUser checks if a user is logging in for the first time and marks them as seen
func (d *D) IsFirstTimeUser(pubkey []byte) (bool, error) {
key := fmt.Sprintf("firstlogin:%s", hex.EncodeToString(pubkey))