firestore.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package firestore
  2. import (
  3. "context"
  4. "time"
  5. fs "cloud.google.com/go/firestore"
  6. "github.com/kellegous/go/internal"
  7. "github.com/kellegous/go/internal/backend"
  8. "golang.org/x/oauth2/google"
  9. "google.golang.org/grpc/codes"
  10. "google.golang.org/grpc/status"
  11. )
  12. var _ backend.Backend = (*Backend)(nil)
  13. // NextID is the next numeric ID to use for auto-generated IDs
  14. type NextID struct {
  15. ID uint32 `json:"id" firestore:"id"`
  16. }
  17. // Backend provides access to Google Firestore.
  18. type Backend struct {
  19. db *fs.Client
  20. }
  21. // New instantiates a new Backend
  22. func New(ctx context.Context, path string) (*Backend, error) {
  23. if path == "" {
  24. path = getGoogleProject()
  25. }
  26. client, err := fs.NewClient(ctx, path)
  27. if err != nil {
  28. return nil, err
  29. }
  30. backend := Backend{
  31. db: client,
  32. }
  33. return &backend, nil
  34. }
  35. // Close the resources associated with this backend.
  36. func (backend *Backend) Close() error {
  37. return backend.db.Close()
  38. }
  39. // Get retreives a shortcut from the data store.
  40. func (backend *Backend) Get(ctx context.Context, name string) (*internal.Route, error) {
  41. ref := backend.db.Doc("routes/" + name)
  42. snap, err := ref.Get(ctx)
  43. if err != nil {
  44. if status.Code(err) == codes.NotFound {
  45. return nil, internal.ErrRouteNotFound
  46. }
  47. return nil, err
  48. }
  49. var rt internal.Route
  50. if err := snap.DataTo(&rt); err != nil {
  51. return nil, err
  52. }
  53. return &rt, nil
  54. }
  55. // Put stores a new shortcut in the data store.
  56. func (backend *Backend) Put(ctx context.Context, key string, rt *internal.Route) error {
  57. ref := backend.db.Doc("routes/" + key)
  58. _, err := ref.Set(ctx, rt)
  59. if err != nil {
  60. return err
  61. }
  62. return nil
  63. }
  64. // Del removes an existing shortcut from the data store.
  65. func (backend *Backend) Del(ctx context.Context, key string) error {
  66. ref := backend.db.Doc("routes/" + key)
  67. _, err := ref.Delete(ctx)
  68. if err != nil {
  69. return err
  70. }
  71. return nil
  72. }
  73. // List all routes in an iterator, starting with the key prefix of start (which can also be nil).
  74. func (backend *Backend) List(ctx context.Context, start string) (internal.RouteIterator, error) {
  75. col := backend.db.Collection("routes").OrderBy(fs.DocumentID, fs.Asc)
  76. if start != "" {
  77. // we have a starting ID.
  78. col = col.StartAt(start)
  79. }
  80. return &RouteIterator{
  81. ctx: ctx,
  82. db: backend.db,
  83. it: col.Documents(ctx),
  84. }, nil
  85. }
  86. // GetAll gets everything in the db to dump it out for backup purposes
  87. func (backend *Backend) GetAll(ctx context.Context) (map[string]internal.Route, error) {
  88. golinks := map[string]internal.Route{}
  89. col := backend.db.Collection("routes").OrderBy(fs.DocumentID, fs.Asc)
  90. routes, err := col.Documents(ctx).GetAll()
  91. if err != nil {
  92. return nil, err
  93. }
  94. for _, doc := range routes {
  95. var rt internal.Route
  96. if err := doc.DataTo(&rt); err != nil {
  97. return nil, err
  98. }
  99. golinks[doc.Ref.ID] = rt
  100. }
  101. return golinks, nil
  102. }
  103. // NextID generates the next numeric ID to be used for an auto-named shortcut.
  104. func (backend *Backend) NextID(ctx context.Context) (uint64, error) {
  105. ref := backend.db.Doc("IDs/nextID")
  106. var nid uint32
  107. err := backend.db.RunTransaction(ctx, func(ctx context.Context, tx *fs.Transaction) error {
  108. var nextID *NextID
  109. doc, err := tx.Get(ref)
  110. if err != nil {
  111. if status.Code(err) == codes.NotFound {
  112. // this is the very first auto-generated ID, we can make it
  113. // as :1 and return it
  114. nextID = new(NextID)
  115. nextID.ID = 1
  116. nid = 1
  117. err := tx.Create(ref, nextID)
  118. if err != nil {
  119. return err
  120. }
  121. return nil
  122. }
  123. return err
  124. }
  125. if err := doc.DataTo(&nextID); err != nil {
  126. return err
  127. }
  128. nextID.ID += 1
  129. nid = nextID.ID
  130. return tx.Set(ref, &nextID)
  131. })
  132. if err != nil {
  133. return 0, err
  134. }
  135. return uint64(nid), nil
  136. }
  137. func getGoogleProject() string {
  138. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  139. defer cancel()
  140. creds, err := google.FindDefaultCredentials(ctx)
  141. if err != nil {
  142. return ""
  143. }
  144. return creds.ProjectID
  145. }