converters.go raw

   1  // Package orlydbv1 provides type converters between proto messages and Go types.
   2  package orlydbv1
   3  
   4  import (
   5  	"time"
   6  
   7  	"next.orly.dev/pkg/nostr/encoders/event"
   8  	"next.orly.dev/pkg/nostr/encoders/filter"
   9  	"next.orly.dev/pkg/nostr/encoders/kind"
  10  	"next.orly.dev/pkg/nostr/encoders/tag"
  11  	"next.orly.dev/pkg/nostr/encoders/timestamp"
  12  	ntypes "next.orly.dev/pkg/nostr/types"
  13  	"next.orly.dev/pkg/database"
  14  	indextypes "next.orly.dev/pkg/database/indexes/types"
  15  	"next.orly.dev/pkg/interfaces/store"
  16  )
  17  
  18  // EventToProto converts a nostr event.E to a proto Event.
  19  // Binary fields (ID, Pubkey, Sig) are copied directly.
  20  // Tags are converted preserving binary values for e/p tags.
  21  func EventToProto(ev *event.E) *Event {
  22  	if ev == nil {
  23  		return nil
  24  	}
  25  
  26  	pb := &Event{
  27  		Id:        ev.ID,
  28  		Pubkey:    ev.Pubkey,
  29  		CreatedAt: ev.CreatedAt,
  30  		Kind:      uint32(ev.Kind),
  31  		Content:   ev.Content,
  32  		Sig:       ev.Sig,
  33  	}
  34  
  35  	if ev.Tags != nil {
  36  		pb.Tags = make([]*Tag, 0, len(*ev.Tags))
  37  		for _, t := range *ev.Tags {
  38  			pbTag := &Tag{
  39  				Values: make([][]byte, 0, len(t.T)),
  40  			}
  41  			for _, v := range t.T {
  42  				// Copy bytes directly to preserve binary data
  43  				val := make([]byte, len(v))
  44  				copy(val, v)
  45  				pbTag.Values = append(pbTag.Values, val)
  46  			}
  47  			pb.Tags = append(pb.Tags, pbTag)
  48  		}
  49  	}
  50  
  51  	return pb
  52  }
  53  
  54  // ProtoToEvent converts a proto Event to a nostr event.E.
  55  func ProtoToEvent(pb *Event) *event.E {
  56  	if pb == nil {
  57  		return nil
  58  	}
  59  
  60  	ev := event.New()
  61  	ev.ID = pb.Id
  62  	ev.Pubkey = pb.Pubkey
  63  	ev.CreatedAt = pb.CreatedAt
  64  	ev.Kind = uint16(pb.Kind)
  65  	ev.Content = pb.Content
  66  	ev.Sig = pb.Sig
  67  
  68  	if len(pb.Tags) > 0 {
  69  		tags := tag.NewSWithCap(len(pb.Tags))
  70  		for _, pbTag := range pb.Tags {
  71  			t := tag.NewWithCap(len(pbTag.Values))
  72  			for _, v := range pbTag.Values {
  73  				t.T = append(t.T, v)
  74  			}
  75  			*tags = append(*tags, t)
  76  		}
  77  		ev.Tags = tags
  78  	}
  79  
  80  	return ev
  81  }
  82  
  83  // FilterToProto converts a nostr filter.F to a proto Filter.
  84  func FilterToProto(f *filter.F) *Filter {
  85  	if f == nil {
  86  		return nil
  87  	}
  88  
  89  	pb := &Filter{}
  90  
  91  	// IDs
  92  	if f.Ids != nil && len(f.Ids.T) > 0 {
  93  		pb.Ids = make([][]byte, 0, len(f.Ids.T))
  94  		for _, id := range f.Ids.T {
  95  			pb.Ids = append(pb.Ids, id)
  96  		}
  97  	}
  98  
  99  	// Kinds
 100  	if f.Kinds != nil && f.Kinds.Len() > 0 {
 101  		pb.Kinds = make([]uint32, 0, f.Kinds.Len())
 102  		for _, k := range f.Kinds.K {
 103  			pb.Kinds = append(pb.Kinds, uint32(k.K))
 104  		}
 105  	}
 106  
 107  	// Authors
 108  	if f.Authors != nil && len(f.Authors.T) > 0 {
 109  		pb.Authors = make([][]byte, 0, len(f.Authors.T))
 110  		for _, a := range f.Authors.T {
 111  			pb.Authors = append(pb.Authors, a)
 112  		}
 113  	}
 114  
 115  	// Tags (e.g., #e, #p, #t)
 116  	if f.Tags != nil && len(*f.Tags) > 0 {
 117  		pb.Tags = make(map[string]*TagSet)
 118  		for _, t := range *f.Tags {
 119  			if len(t.T) >= 2 {
 120  				key := string(t.T[0])
 121  				ts, exists := pb.Tags[key]
 122  				if !exists {
 123  					ts = &TagSet{}
 124  					pb.Tags[key] = ts
 125  				}
 126  				// Add all values after the key
 127  				for _, v := range t.T[1:] {
 128  					ts.Values = append(ts.Values, v)
 129  				}
 130  			}
 131  		}
 132  	}
 133  
 134  	// Since
 135  	if f.Since != nil {
 136  		since := f.Since.I64()
 137  		pb.Since = &since
 138  	}
 139  
 140  	// Until
 141  	if f.Until != nil {
 142  		until := f.Until.I64()
 143  		pb.Until = &until
 144  	}
 145  
 146  	// Search
 147  	if len(f.Search) > 0 {
 148  		pb.Search = f.Search
 149  	}
 150  
 151  	// Limit
 152  	if f.Limit != nil {
 153  		limit := uint32(*f.Limit)
 154  		pb.Limit = &limit
 155  	}
 156  
 157  	return pb
 158  }
 159  
 160  // ProtoToFilter converts a proto Filter to a nostr filter.F.
 161  func ProtoToFilter(pb *Filter) *filter.F {
 162  	if pb == nil {
 163  		return nil
 164  	}
 165  
 166  	f := filter.New()
 167  
 168  	// IDs
 169  	if len(pb.Ids) > 0 {
 170  		f.Ids = tag.NewWithCap(len(pb.Ids))
 171  		for _, id := range pb.Ids {
 172  			f.Ids.T = append(f.Ids.T, id)
 173  		}
 174  	}
 175  
 176  	// Kinds
 177  	if len(pb.Kinds) > 0 {
 178  		kinds := kind.NewWithCap(len(pb.Kinds))
 179  		for _, k := range pb.Kinds {
 180  			kinds.K = append(kinds.K, kind.New(uint16(k)))
 181  		}
 182  		f.Kinds = kinds
 183  	}
 184  
 185  	// Authors
 186  	if len(pb.Authors) > 0 {
 187  		f.Authors = tag.NewWithCap(len(pb.Authors))
 188  		for _, a := range pb.Authors {
 189  			f.Authors.T = append(f.Authors.T, a)
 190  		}
 191  	}
 192  
 193  	// Tags
 194  	if len(pb.Tags) > 0 {
 195  		tags := tag.NewSWithCap(len(pb.Tags))
 196  		for key, ts := range pb.Tags {
 197  			for _, v := range ts.Values {
 198  				t := tag.NewWithCap(2)
 199  				t.T = append(t.T, []byte(key), v)
 200  				*tags = append(*tags, t)
 201  			}
 202  		}
 203  		f.Tags = tags
 204  	}
 205  
 206  	// Since
 207  	if pb.Since != nil {
 208  		f.Since = timestamp.FromUnix(*pb.Since)
 209  	}
 210  
 211  	// Until
 212  	if pb.Until != nil {
 213  		f.Until = timestamp.FromUnix(*pb.Until)
 214  	}
 215  
 216  	// Search
 217  	if len(pb.Search) > 0 {
 218  		f.Search = pb.Search
 219  	}
 220  
 221  	// Limit
 222  	if pb.Limit != nil {
 223  		limit := uint(*pb.Limit)
 224  		f.Limit = &limit
 225  	}
 226  
 227  	return f
 228  }
 229  
 230  // Uint40ToProto converts a indextypes.Uint40 to a proto Uint40.
 231  func Uint40ToProto(u *indextypes.Uint40) *Uint40 {
 232  	if u == nil {
 233  		return nil
 234  	}
 235  	return &Uint40{Value: u.Get()}
 236  }
 237  
 238  // ProtoToUint40 converts a proto Uint40 to a indextypes.Uint40.
 239  func ProtoToUint40(pb *Uint40) *indextypes.Uint40 {
 240  	if pb == nil {
 241  		return nil
 242  	}
 243  	return newUint40(pb.Value)
 244  }
 245  
 246  // Uint40sToProto converts a slice of Uint40s to a SerialList.
 247  func Uint40sToProto(serials indextypes.Uint40s) *SerialList {
 248  	result := &SerialList{
 249  		Serials: make([]uint64, 0, len(serials)),
 250  	}
 251  	for _, s := range serials {
 252  		result.Serials = append(result.Serials, s.Get())
 253  	}
 254  	return result
 255  }
 256  
 257  // ProtoToUint40s converts a SerialList to a slice of Uint40 pointers.
 258  func ProtoToUint40s(pb *SerialList) []*indextypes.Uint40 {
 259  	if pb == nil {
 260  		return nil
 261  	}
 262  	result := make([]*indextypes.Uint40, 0, len(pb.Serials))
 263  	for _, s := range pb.Serials {
 264  		result = append(result, newUint40(s))
 265  	}
 266  	return result
 267  }
 268  
 269  // IdPkTsToProto converts a store.IdPkTs to a proto IdPkTs.
 270  func IdPkTsToProto(i *store.IdPkTs) *IdPkTs {
 271  	if i == nil {
 272  		return nil
 273  	}
 274  	return &IdPkTs{
 275  		Id:        i.Id[:],
 276  		Pubkey:    i.Pub[:],
 277  		Timestamp: i.Ts,
 278  		Serial:    i.Ser,
 279  	}
 280  }
 281  
 282  // ProtoToIdPkTs converts a proto IdPkTs to a store.IdPkTs.
 283  func ProtoToIdPkTs(pb *IdPkTs) *store.IdPkTs {
 284  	if pb == nil {
 285  		return nil
 286  	}
 287  	return &store.IdPkTs{
 288  		Id:  ntypes.EventIDFromBytes(pb.Id),
 289  		Pub: ntypes.PubkeyFromBytes(pb.Pubkey),
 290  		Ts:  pb.Timestamp,
 291  		Ser: pb.Serial,
 292  	}
 293  }
 294  
 295  // IdPkTsListToProto converts a slice of IdPkTs to a proto IdPkTsList.
 296  func IdPkTsListToProto(items []*store.IdPkTs) *IdPkTsList {
 297  	result := &IdPkTsList{
 298  		Items: make([]*IdPkTs, 0, len(items)),
 299  	}
 300  	for _, item := range items {
 301  		result.Items = append(result.Items, IdPkTsToProto(item))
 302  	}
 303  	return result
 304  }
 305  
 306  // ProtoToIdPkTsList converts a proto IdPkTsList to a slice of IdPkTs.
 307  func ProtoToIdPkTsList(pb *IdPkTsList) []*store.IdPkTs {
 308  	if pb == nil {
 309  		return nil
 310  	}
 311  	result := make([]*store.IdPkTs, 0, len(pb.Items))
 312  	for _, item := range pb.Items {
 313  		result = append(result, ProtoToIdPkTs(item))
 314  	}
 315  	return result
 316  }
 317  
 318  // SubscriptionToProto converts a database.Subscription to a proto Subscription.
 319  func SubscriptionToProto(s *database.Subscription, pubkey []byte) *Subscription {
 320  	if s == nil {
 321  		return nil
 322  	}
 323  	return &Subscription{
 324  		Pubkey:           pubkey,
 325  		TrialEnd:         s.TrialEnd.Unix(),
 326  		PaidUntil:        s.PaidUntil.Unix(),
 327  		BlossomLevel:     s.BlossomLevel,
 328  		BlossomStorageMb: s.BlossomStorage,
 329  	}
 330  }
 331  
 332  // ProtoToSubscription converts a proto Subscription to a database.Subscription.
 333  func ProtoToSubscription(pb *Subscription) *database.Subscription {
 334  	if pb == nil {
 335  		return nil
 336  	}
 337  	return &database.Subscription{
 338  		TrialEnd:       timeFromUnix(pb.TrialEnd),
 339  		PaidUntil:      timeFromUnix(pb.PaidUntil),
 340  		BlossomLevel:   pb.BlossomLevel,
 341  		BlossomStorage: pb.BlossomStorageMb,
 342  	}
 343  }
 344  
 345  // PaymentToProto converts a database.Payment to a proto Payment.
 346  func PaymentToProto(p *database.Payment) *Payment {
 347  	return &Payment{
 348  		Amount:    p.Amount,
 349  		Timestamp: p.Timestamp.Unix(),
 350  		Invoice:   p.Invoice,
 351  		Preimage:  p.Preimage,
 352  	}
 353  }
 354  
 355  // ProtoToPayment converts a proto Payment to a database.Payment.
 356  func ProtoToPayment(pb *Payment) *database.Payment {
 357  	return &database.Payment{
 358  		Amount:    pb.Amount,
 359  		Timestamp: timeFromUnix(pb.Timestamp),
 360  		Invoice:   pb.Invoice,
 361  		Preimage:  pb.Preimage,
 362  	}
 363  }
 364  
 365  // PaymentListToProto converts a slice of payments to a proto PaymentList.
 366  func PaymentListToProto(payments []database.Payment) *PaymentList {
 367  	result := &PaymentList{
 368  		Payments: make([]*Payment, 0, len(payments)),
 369  	}
 370  	for _, p := range payments {
 371  		result.Payments = append(result.Payments, PaymentToProto(&p))
 372  	}
 373  	return result
 374  }
 375  
 376  // ProtoToPaymentList converts a proto PaymentList to a slice of payments.
 377  func ProtoToPaymentList(pb *PaymentList) []database.Payment {
 378  	if pb == nil {
 379  		return nil
 380  	}
 381  	result := make([]database.Payment, 0, len(pb.Payments))
 382  	for _, p := range pb.Payments {
 383  		result = append(result, *ProtoToPayment(p))
 384  	}
 385  	return result
 386  }
 387  
 388  // NIP43MembershipToProto converts a database.NIP43Membership to a proto NIP43Membership.
 389  func NIP43MembershipToProto(m *database.NIP43Membership) *NIP43Membership {
 390  	if m == nil {
 391  		return nil
 392  	}
 393  	return &NIP43Membership{
 394  		Pubkey:     m.Pubkey,
 395  		AddedAt:    m.AddedAt.Unix(),
 396  		InviteCode: m.InviteCode,
 397  	}
 398  }
 399  
 400  // ProtoToNIP43Membership converts a proto NIP43Membership to a database.NIP43Membership.
 401  func ProtoToNIP43Membership(pb *NIP43Membership) *database.NIP43Membership {
 402  	if pb == nil {
 403  		return nil
 404  	}
 405  	return &database.NIP43Membership{
 406  		Pubkey:     pb.Pubkey,
 407  		AddedAt:    timeFromUnix(pb.AddedAt),
 408  		InviteCode: pb.InviteCode,
 409  	}
 410  }
 411  
 412  // RangeToProto converts a database.Range to a proto Range.
 413  func RangeToProto(r database.Range) *Range {
 414  	return &Range{
 415  		Prefix: r.Start,
 416  		Start:  0,
 417  		End:    0,
 418  	}
 419  }
 420  
 421  // ProtoToRange converts a proto Range to a database.Range.
 422  func ProtoToRange(pb *Range) database.Range {
 423  	if pb == nil {
 424  		return database.Range{}
 425  	}
 426  	return database.Range{
 427  		Start: pb.Prefix,
 428  		End:   nil,
 429  	}
 430  }
 431  
 432  // EventMapToProto converts a map of serial->event to a proto EventMap.
 433  func EventMapToProto(events map[uint64]*event.E) *EventMap {
 434  	result := &EventMap{
 435  		Events: make(map[uint64]*Event),
 436  	}
 437  	for serial, ev := range events {
 438  		result.Events[serial] = EventToProto(ev)
 439  	}
 440  	return result
 441  }
 442  
 443  // ProtoToEventMap converts a proto EventMap to a map of serial->event.
 444  func ProtoToEventMap(pb *EventMap) map[uint64]*event.E {
 445  	if pb == nil {
 446  		return nil
 447  	}
 448  	result := make(map[uint64]*event.E)
 449  	for serial, ev := range pb.Events {
 450  		result[serial] = ProtoToEvent(ev)
 451  	}
 452  	return result
 453  }
 454  
 455  // EventsToProto converts a slice of events to a slice of proto Events.
 456  func EventsToProto(events event.S) []*Event {
 457  	result := make([]*Event, 0, len(events))
 458  	for _, ev := range events {
 459  		result = append(result, EventToProto(ev))
 460  	}
 461  	return result
 462  }
 463  
 464  // ProtoToEvents converts a slice of proto Events to a slice of events.
 465  func ProtoToEvents(pbs []*Event) event.S {
 466  	result := make(event.S, 0, len(pbs))
 467  	for _, pb := range pbs {
 468  		result = append(result, ProtoToEvent(pb))
 469  	}
 470  	return result
 471  }
 472  
 473  // timeFromUnix converts a unix timestamp to time.Time (unexported).
 474  func timeFromUnix(unix int64) time.Time {
 475  	if unix == 0 {
 476  		return time.Time{}
 477  	}
 478  	return time.Unix(unix, 0)
 479  }
 480  
 481  // TimeFromUnix converts a unix timestamp to time.Time (exported).
 482  func TimeFromUnix(unix int64) time.Time {
 483  	return timeFromUnix(unix)
 484  }
 485  
 486  // newUint40 creates a new Uint40 with the given value.
 487  func newUint40(value uint64) *indextypes.Uint40 {
 488  	u := &indextypes.Uint40{}
 489  	_ = u.Set(value)
 490  	return u
 491  }
 492  
 493  // BytesToTag converts a slice of byte slices to a tag.T.
 494  func BytesToTag(ids [][]byte) *tag.T {
 495  	t := tag.NewWithCap(len(ids))
 496  	for _, id := range ids {
 497  		t.T = append(t.T, id)
 498  	}
 499  	return t
 500  }
 501  
 502  // === Blob Storage Converters ===
 503  
 504  // BlobMetadataToProto converts a database.BlobMetadata to a proto BlobMetadata.
 505  func BlobMetadataToProto(m *database.BlobMetadata) *BlobMetadata {
 506  	if m == nil {
 507  		return nil
 508  	}
 509  	return &BlobMetadata{
 510  		Pubkey:    m.Pubkey,
 511  		MimeType:  m.MimeType,
 512  		Size:      m.Size,
 513  		Uploaded:  m.Uploaded,
 514  		Extension: m.Extension,
 515  	}
 516  }
 517  
 518  // ProtoToBlobMetadata converts a proto BlobMetadata to a database.BlobMetadata.
 519  func ProtoToBlobMetadata(pb *BlobMetadata) *database.BlobMetadata {
 520  	if pb == nil {
 521  		return nil
 522  	}
 523  	return &database.BlobMetadata{
 524  		Pubkey:    pb.Pubkey,
 525  		MimeType:  pb.MimeType,
 526  		Size:      pb.Size,
 527  		Uploaded:  pb.Uploaded,
 528  		Extension: pb.Extension,
 529  	}
 530  }
 531  
 532  // BlobDescriptorToProto converts a database.BlobDescriptor to a proto BlobDescriptor.
 533  // Note: NIP94 field is not transported via gRPC as it's generated at response time.
 534  func BlobDescriptorToProto(d *database.BlobDescriptor) *BlobDescriptor {
 535  	if d == nil {
 536  		return nil
 537  	}
 538  	return &BlobDescriptor{
 539  		Url:      d.URL,
 540  		Sha256:   d.SHA256,
 541  		Size:     d.Size,
 542  		Type:     d.Type,
 543  		Uploaded: d.Uploaded,
 544  	}
 545  }
 546  
 547  // ProtoToBlobDescriptor converts a proto BlobDescriptor to a database.BlobDescriptor.
 548  // Note: NIP94 field is not populated from proto as it's generated at response time.
 549  func ProtoToBlobDescriptor(pb *BlobDescriptor) *database.BlobDescriptor {
 550  	if pb == nil {
 551  		return nil
 552  	}
 553  	return &database.BlobDescriptor{
 554  		URL:      pb.Url,
 555  		SHA256:   pb.Sha256,
 556  		Size:     pb.Size,
 557  		Type:     pb.Type,
 558  		Uploaded: pb.Uploaded,
 559  	}
 560  }
 561  
 562  // BlobDescriptorListToProto converts a slice of BlobDescriptors to proto.
 563  func BlobDescriptorListToProto(descriptors []*database.BlobDescriptor) []*BlobDescriptor {
 564  	result := make([]*BlobDescriptor, 0, len(descriptors))
 565  	for _, d := range descriptors {
 566  		result = append(result, BlobDescriptorToProto(d))
 567  	}
 568  	return result
 569  }
 570  
 571  // ProtoToBlobDescriptorList converts proto BlobDescriptors to a slice.
 572  func ProtoToBlobDescriptorList(pbs []*BlobDescriptor) []*database.BlobDescriptor {
 573  	result := make([]*database.BlobDescriptor, 0, len(pbs))
 574  	for _, pb := range pbs {
 575  		result = append(result, ProtoToBlobDescriptor(pb))
 576  	}
 577  	return result
 578  }
 579  
 580  // UserBlobStatsToProto converts a database.UserBlobStats to a proto UserBlobStats.
 581  func UserBlobStatsToProto(s *database.UserBlobStats) *UserBlobStats {
 582  	if s == nil {
 583  		return nil
 584  	}
 585  	return &UserBlobStats{
 586  		PubkeyHex:      s.PubkeyHex,
 587  		BlobCount:      s.BlobCount,
 588  		TotalSizeBytes: s.TotalSizeBytes,
 589  	}
 590  }
 591  
 592  // ProtoToUserBlobStats converts a proto UserBlobStats to a database.UserBlobStats.
 593  func ProtoToUserBlobStats(pb *UserBlobStats) *database.UserBlobStats {
 594  	if pb == nil {
 595  		return nil
 596  	}
 597  	return &database.UserBlobStats{
 598  		PubkeyHex:      pb.PubkeyHex,
 599  		BlobCount:      pb.BlobCount,
 600  		TotalSizeBytes: pb.TotalSizeBytes,
 601  	}
 602  }
 603  
 604  // UserBlobStatsListToProto converts a slice of UserBlobStats to proto.
 605  func UserBlobStatsListToProto(stats []*database.UserBlobStats) []*UserBlobStats {
 606  	result := make([]*UserBlobStats, 0, len(stats))
 607  	for _, s := range stats {
 608  		result = append(result, UserBlobStatsToProto(s))
 609  	}
 610  	return result
 611  }
 612  
 613  // ProtoToUserBlobStatsList converts proto UserBlobStats to a slice.
 614  func ProtoToUserBlobStatsList(pbs []*UserBlobStats) []*database.UserBlobStats {
 615  	result := make([]*database.UserBlobStats, 0, len(pbs))
 616  	for _, pb := range pbs {
 617  		result = append(result, ProtoToUserBlobStats(pb))
 618  	}
 619  	return result
 620  }
 621  
 622  // === Paid ACL Converters ===
 623  
 624  // PaidSubscriptionToProto converts a database.PaidSubscription to proto.
 625  func PaidSubscriptionToProto(s *database.PaidSubscription) *PaidSubscriptionMsg {
 626  	if s == nil {
 627  		return nil
 628  	}
 629  	return &PaidSubscriptionMsg{
 630  		PubkeyHex:   s.PubkeyHex,
 631  		Alias:       s.Alias,
 632  		ExpiresAt:   s.ExpiresAt.Unix(),
 633  		CreatedAt:   s.CreatedAt.Unix(),
 634  		InvoiceHash: s.InvoiceHash,
 635  	}
 636  }
 637  
 638  // ProtoToPaidSubscription converts a proto PaidSubscriptionMsg to database type.
 639  func ProtoToPaidSubscription(pb *PaidSubscriptionMsg) *database.PaidSubscription {
 640  	if pb == nil {
 641  		return nil
 642  	}
 643  	return &database.PaidSubscription{
 644  		PubkeyHex:   pb.PubkeyHex,
 645  		Alias:       pb.Alias,
 646  		ExpiresAt:   timeFromUnix(pb.ExpiresAt),
 647  		CreatedAt:   timeFromUnix(pb.CreatedAt),
 648  		InvoiceHash: pb.InvoiceHash,
 649  	}
 650  }
 651  
 652  // PaidSubscriptionListToProto converts a slice of PaidSubscriptions to proto.
 653  func PaidSubscriptionListToProto(subs []*database.PaidSubscription) *PaidSubscriptionList {
 654  	result := &PaidSubscriptionList{
 655  		Subscriptions: make([]*PaidSubscriptionMsg, 0, len(subs)),
 656  	}
 657  	for _, s := range subs {
 658  		result.Subscriptions = append(result.Subscriptions, PaidSubscriptionToProto(s))
 659  	}
 660  	return result
 661  }
 662  
 663  // ProtoToPaidSubscriptionList converts a proto PaidSubscriptionList to a slice.
 664  func ProtoToPaidSubscriptionList(pb *PaidSubscriptionList) []*database.PaidSubscription {
 665  	if pb == nil {
 666  		return nil
 667  	}
 668  	result := make([]*database.PaidSubscription, 0, len(pb.Subscriptions))
 669  	for _, s := range pb.Subscriptions {
 670  		result = append(result, ProtoToPaidSubscription(s))
 671  	}
 672  	return result
 673  }
 674